ParamSpec captures function parameter types for decorators.
1from typing import ParamSpec, TypeVar, Callable23P = ParamSpec("P")4R = TypeVar("R")56# Type-safe decorator7def log_calls(func: Callable[P, R]) -> Callable[P, R]:8 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:9 print(f"Calling {func.__name__}")10 return func(*args, **kwargs)11 return wrapper1213@log_calls14def add(a: int, b: int) -> int:15 return a + b1617add(1, 2) # Type checker knows: (int, int) -> int1819# With keyword arguments20@log_calls21def greet(name: str, greeting: str = "Hello") -> str:22 return f"{greeting}, {name}!"2324greet("Alice") # OK25greet("Alice", greeting="Hi") # OK2627# ParamSpec with Concatenate28from typing import Concatenate2930def with_context(31 func: Callable[Concatenate[Context, P], R]32) -> Callable[P, R]:33 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:34 ctx = get_context()35 return func(ctx, *args, **kwargs)36 return wrapper3738@with_context39def process(ctx: Context, data: str) -> str:40 return ctx.transform(data)4142process("hello") # Context is injected, type-safe!
Benefits: