Decorators: Closure Binding, Parameterized Wrappers, and functools.wraps
- Last updated
- Prerequisites:
- Python First-Class Functions and Closures
- Scope resolution (`*args`, `**kwargs`)
- python
- decorators
- functools
- wraps
- closures
- metaprogramming
Read at your depth
The practical view
A decorator is a function that takes another function as an argument and returns an augmented function: `@my_decorator def hello(): ...` is syntactic sugar for `hello = my_decorator(hello)`. Parameterized decorators require an extra outer function layer: `@repeat(num=3)`. Always use `@functools.wraps(fn)` on your wrapper function to preserve the original function's name (`__name__`), docstring (`__doc__`), and type annotations.
The same idea in other frameworks
`@decorator` syntax with `functools.wraps(fn)`.TypeScript Method/Class Decorators (`@timing`).Java Annotations with Spring AOP `@Around` advice.Rust attribute-like procedural macros (`#[trace]`).Legacy vs modern
Naive Decorator (Lost Metadata) vs functools.wraps Decorator
Naive decorators obliterate `__name__` and `__doc__`; `@functools.wraps` preserves metadata and the `__wrapped__` pointer.
def wrapper(*args, **kwargs): ... return wrapper // ❌ Overwrites __name__ with 'wrapper'@functools.wraps(fn)
def wrapper(*args, **kwargs): ... // ✅ NEW: Preserves name, docstring & signatureInterview gotchas
Context
Interviewers test your knowledge of `inspect` and un-wrapping hooks.
The mechanical answer
Access the **`__wrapped__`** attribute: `original_fn = decorated_fn.__wrapped__`. `@functools.wraps` automatically attaches a reference to the original function on `__wrapped__`, allowing unit tests and dependency injectors to bypass middleware or timing wrappers during testing.
Trap
If a function is decorated with multiple chained decorators, `__wrapped__` points to the immediately preceding wrapper; use `inspect.unwrap(fn)` to unwrap all chained layers completely.
Context
Checks mechanical understanding of decorator syntax evaluation.
The mechanical answer
Because `@repeat(num=3)` is an immediate function call. Level 1 (`repeat(num=3)`) receives the decorator arguments and returns the actual decorator. Level 2 (`decorator(fn)`) receives the function to be decorated and returns the wrapper. Level 3 (`wrapper(*args, **kwargs)`) receives the runtime arguments when the decorated function is actually called.
Trap
Writing only two levels for a parameterized decorator will pass the function to the parameter argument instead of the target function.