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).
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__)