Advanced Decorators
Parameterised decorators, class decorators, stacking, and the functools toolkit
def or class statement executes, and it can rewrite the object's identity, add state, register it somewhere, or accept its own configuration. The standard library ships production-grade decorators in functools that you should reach for before writing your own.
- write a parameterised decorator (a decorator factory) and explain its three call layers,
- build a decorator that works with or without arguments,
- distinguish the two meanings of class decorator and implement both,
- reason precisely about the order in which stacked decorators apply and run,
- apply
functools.wraps,lru_cache/cache, andsingledispatch, and state their real trade-offs.
1. Parameterised decorators (decorator factories)
A plain decorator takes exactly one argument: the function being decorated. So how does @lru_cache(maxsize=256) take arguments? The trick is one extra layer. @repeat(times=3) is not a decorator; it is a function that returns a decorator. Python first evaluates repeat(times=3), and whatever that returns is then used as the decorator. This gives a parameterised decorator three nested layers.
import functools
def repeat(times): # layer 1: the factory, takes the config
def decorator(func): # layer 2: the real decorator, takes the function
@functools.wraps(func)
def wrapper(*args, **kwargs): # layer 3: the wrapper, takes the call args
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def ping():
print("ping")
Read @repeat(times=3) as two steps: d = repeat(times=3) returns decorator, then ping = d(ping). The outermost times is captured by the closure, so every call to the decorated ping repeats three times.