Framework authors and decorator libraries need precise control over what tools see.
wraps(func) is partial(update_wrapper, wrapped=func). update_wrapper(wrapper, wrapped):
1. Copies each attribute in WRAPPER_ASSIGNMENTS (__module__, __name__, __qualname__, __doc__, __annotations__, __type_params__) if present on wrapped. 2. Updates attributes in WRAPPER_UPDATES (__dict__) by merging, so custom attributes set on the original survive. 3. Sets wrapper.__wrapped__ = wrapped, after the copying, so it isn't overwritten by a copied __wrapped__.
You can pass custom assigned/updated tuples, for example to skip copying __annotations__ when the wrapper changes types.
__wrapped__ forms a chain through stacked decorators. inspect.unwrap(f) follows it to the innermost function; inspect.signature follows it too, unless the wrapper defines __signature__. That's the hook for signature-changing decorators: if your decorator injects or removes a parameter (dependency injection, a retries= option), set wrapper.__signature__ to an adjusted inspect.Signature so introspection matches what callers may actually pass.
Class-based decorators should call update_wrapper(self, func) in __init__, and implement __get__ if they must work on methods, since instances of ordinary classes don't bind self like functions do.
Example
import inspect
from functools import wraps
def with_retries(func):
@wraps(func)
def wrapper(*args, retries=3, **kwargs):
for attempt in range(retries):
try:
return func(*args, **kwargs)
except ConnectionError:
if attempt == retries - 1:
raise
sig = inspect.signature(func)
extra = inspect.Parameter("retries", inspect.Parameter.KEYWORD_ONLY, default=3)
wrapper.__signature__ = sig.replace(parameters=[*sig.parameters.values(), extra])
return wrapper
@with_retries
def fetch(url: str) -> str:
return f"GET {url}"
print(inspect.signature(fetch)) # (url: str, *, retries=3) -> str
print(inspect.unwrap(fetch) is fetch.__wrapped__) # True
print(fetch("x", retries=1)) # GET xRemember
wraps=update_wrapper: copy metadata, merge__dict__, set__wrapped__inspect.unwrapandinspect.signaturefollow__wrapped__chains- Set
__signature__when a decorator changes the accepted parameters
Watch out for
- Writing a class-based decorator without
__get__and finding it breaks when applied to methods.