What does `functools.wraps` do, and why is it important?

5 minintermediatefunctionsdecoratorsfunctools

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 — without wraps, 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.