Mastery
Mastery/Python/C. Functions & closures
T1 · high-leverage

Decorators silently destroy metadata unless you use functools.wraps

A decorator that returns a plain wrapper function replaces the original function's __name__, __doc__, __module__, etc. with the wrapper's own. This breaks introspection, debuggers, help(), and tools that read docstrings — and it's an extremely common bug in hand-written decorators. functools.wraps copies that metadata onto the wrapper (and sets __wrapped__ to point back to the original).

python
import functools

def logged_naive(fn):
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)
    return wrapper

def logged_correct(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)
    return wrapper

@logged_naive
def greet(name):
    """Say hello to someone."""
    return f"hi {name}"

@logged_correct
def greet2(name):
    """Say hello to someone."""
    return f"hi {name}"

print("naive:  ", greet.__name__, "|", greet.__doc__)
print("correct:", greet2.__name__, "|", greet2.__doc__)
print("wraps also sets __wrapped__:", greet2.__wrapped__ is greet2.__wrapped__)

Interview angle

A frequent follow-up once a candidate demonstrates they can write a decorator: "what's missing?" It's a great filter because it separates people who can write a decorator from people who write correct, debuggable ones — and it opens naturally into a discussion of introspection (__name__, __doc__) and why tooling depends on it.

In the industry

Forgetting functools.wraps is a genuinely-shipped bug class: it silently breaks help(), documentation generators (Sphinx), debuggers, and anything that introspects __name__ — including some test frameworks and caching layers keyed by function identity. It's the kind of bug that doesn't fail loudly; it just quietly degrades tooling until someone spends an hour confused about why a stack trace or a docs page shows the wrong function name.