What does `functools.wraps` do, and why is it important?
Quick Answer
Without it, a decorator's wrapper function **replaces** the original function's `__name__`, `__doc__`, and other metadata with the wrapper's own — so `help(func)`, `func.__name__`, and introspection tools see the wrapper, not the original. `@functools.wraps(func)` copies that metadata onto the wrapper, preserving the illusion that the decorated function is still "itself" for debugging, documentation, and tools that rely on `__name__`/`__wrapped__`.
Detailed Answer
The problem without wraps
def logged(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logged
def add(a, b):
"""Add two numbers."""
return a + b
add.__name__ # 'wrapper' -- lost the real name!
add.__doc__ # None -- lost the docstring!
help(add) # shows wrapper's (empty) signature/doc, not add's
Every function decorated with logged reports its name as "wrapper" and
loses its docstring — this breaks help(), auto-generated API docs,
debuggers, and any code that inspects func.__name__ (e.g., logging
frameworks that print the function name).
The fix
import functools
def logged(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logged
def add(a, b):
"""Add two numbers."""
return a + b
add.__name__ # 'add'
add.__doc__ # 'Add two numbers.'
add.__wrapped__ # <function add at ...> -- the original, un-decorated function
functools.wraps(func) is itself a decorator (built on
functools.update_wrapper) that copies __name__, __doc__, __module__,
__qualname__, and __dict__ from func onto wrapper, and sets
wrapper.__wrapped__ = func so introspection tools can find the original
if needed.
Why this matters beyond cosmetics
- Debugging/tracebacks: stack traces show the wrapper's name unless metadata is copied, making it harder to tell which decorated function actually failed.
- Documentation tools (Sphinx,
help()) rely on__doc__/__name__to generate correct docs — withoutwraps, every decorated function's docs would show up as generic wrapper boilerplate. - Other decorators/frameworks that inspect
__name__(e.g., some testing or routing frameworks match handlers by function name) would silently break.
Interview-ready summary: A decorator's inner wrapper function shadows
the original function's identity (__name__, __doc__, etc.) unless you
explicitly restore it with @functools.wraps(func) — treat it as
mandatory boilerplate any time you write a decorator, not an optional
nicety.