Without wraps, decorators break help(), logging, debugging tools and frameworks that inspect functions.
A decorator replaces a function with a wrapper. Without help, the wrapper's metadata is the wrapper's own: __name__ becomes "wrapper", the docstring disappears, and inspect.signature shows (*args, **kwargs). That breaks help(), error messages, logging that prints function names, and frameworks like FastAPI or pytest that inspect signatures.
@functools.wraps(func) on the inner function copies the metadata over:
def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperIt copies __module__, __name__, __qualname__, __doc__, __annotations__ and __type_params__, merges the wrapper's __dict__ with the original's, and sets `__wrapped__` pointing to the original function. inspect.signature follows __wrapped__, so the real signature shows up, and you can reach the undecorated function via f.__wrapped__ in tests.
Decorators with arguments need one more level of nesting: an outer function takes the arguments and returns the actual decorator.
Make it a habit: every function-wrapping decorator you write should use @wraps.
Example
import inspect
import time
from functools import wraps
def timed(label):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
print(f"[{label}] {func.__name__} took {time.perf_counter() - start:.0f}s")
return wrapper
return decorator
@timed("db")
def fetch(user_id: int, *, fresh: bool = False) -> dict:
"""Load a user record."""
return {"id": user_id}
print(fetch(7)) # [db] fetch took 0s then {'id': 7}
print(fetch.__name__, fetch.__doc__) # fetch Load a user record.
print(inspect.signature(fetch)) # (user_id: int, *, fresh: bool = False) -> dict
print(fetch.__wrapped__(8)) # {'id': 8} (undecorated)Remember
- Use
@wraps(func)on every wrapper function - It copies name, docstring and annotations and sets
__wrapped__ - Decorators with arguments add an outer factory function
Watch out for
- Writing decorators without
wraps, so every decorated function appears as 'wrapper' in logs and tracebacks.