functools.wraps preserves identity and metadata so introspection tools keep working
Without functools.wraps, the object bound to the decorated name is the wrapper, so name, doc, module, qualname, and dict all describe the wrapper instead of the original function. That breaks documentation generators, debuggers, tracebacks, logging, and frameworks that read function attributes at import time. functools.wraps copies those attributes from the wrapped function onto the wrapper and, since Python 3.2, sets wrapped so tools like inspect.signature and inspect.unwrap can follow the chain back to the original. It is essentially functools.update_wrapper with sensible defaults.
functools.WRAPPER_ASSIGNMENTS covers module, name, qualname, annotations, and doc.
It also updates dict and sets wrapped, which inspect.signature follows by default.
Trade-off: wraps makes the wrapper masquerade as the original. That is usually what you want, but it can hide instrumentation from tools that explicitly unwrap.
Common mistake: manually assigning only wrapper.name = fn.name and thinking that is enough. Signature, annotations, and docstring still lie.
Common mistake: assuming wraps changes the actual call signature. It only changes metadata; if a tool enforces the real signature you need a signature-preserving wrapper.
Version note: wrapped and the automatic assignment in functools.wraps were added in Python 3.2. functools.WRAPPER_ASSIGNMENTS is a stable public constant you can customize.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience