Decorators#
Decorators wrap functions (or classes) to extend behavior without modifying source. They are syntactic sugar for func = decorator(func) and are built on Closures.
How to use this page
Master closures first. Used in timing, logging, auth, caching, and validation patterns.
At a glance
| Track | Python Advanced → Advanced Functions |
| Sections | 7 major topics |
| Outline | Use the right-hand TOC to jump |
Topics: What a decorator is · Preserving metadata — functools.wraps · Decorators with arguments (three levels) · Common decorator patterns · Stacking decorators · Class decorators · staticmethod / classmethod as decorators
- What a decorator is
- Preserving metadata —
functools.wraps - Decorators with arguments (three levels)
- Common decorator patterns
- Stacking decorators
- Class decorators
staticmethod/classmethodas decorators
What a decorator is#
def add_logging(fn):
def wrapper(*args, **kwargs):
print(f"calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
@add_logging
def greet(name: str):
print(f"Hello, {name}")
# Equivalent to: greet = add_logging(greet)
greet("Alice")
The @ syntax applies decorator at function definition time (when module loads).
Preserving metadata — functools.wraps#
Bare wrappers hide original name/docstring:
from functools import wraps
def add_logging(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print(f"calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
greet.__name__ # 'greet' with wraps; 'wrapper' without
Always use @wraps(fn) on wrapper functions.
Decorators with arguments (three levels)#
from functools import wraps
def repeat(times: int):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = fn(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say(msg: str):
print(msg)
Pattern: outer takes decorator config → middle takes fn → inner is wrapper.
Common decorator patterns#
Timing#
import time
from functools import wraps
def timer(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
print(f"{fn.__name__}: {time.perf_counter() - start:.4f}s")
return wrapper
Caching#
from functools import cache, lru_cache
@cache
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
@lru_cache(maxsize=128)
def expensive(a: int, b: int) -> int:
...
Prefer stdlib @cache over hand-rolled memoization.
Validation#
def require_positive(fn):
@wraps(fn)
def wrapper(x: int, *args, **kwargs):
if x <= 0:
raise ValueError("x must be positive")
return fn(x, *args, **kwargs)
return wrapper
Register / plugin pattern#
REGISTRY: dict[str, callable] = {}
def register(name: str):
def decorator(fn):
REGISTRY[name] = fn
return fn
return decorator
@register("sum")
def add(a, b):
return a + b
Stacking decorators#
Applied bottom-up (closest to def first):
Order matters when decorators transform signatures or side effects.
Class decorators#
def singleton(cls):
instances = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
pass
Also: @dataclass, @total_ordering. See Class Decorators.
staticmethod / classmethod as decorators#
Built-in decorators change descriptor behavior on classes — different mechanism from function wrappers.
Interview traps (quick reference)#
| Trap | What goes wrong | Safe approach |
|---|---|---|
Missing @wraps |
Lost __name__, __doc__ |
Always wrap with wraps |
| Decorator order | Wrong application order | Remember bottom-up |
| Stateful decorator | Shared mutable default | New dict inside factory |
@cache on mutable args |
Unhashable TypeError | Tuple args |
| Decorator changes signature | Breaks introspection | Use wraps, consider typing.ParamSpec |
Mental model checklist#
- What does
@decodesugar to? - Why three nested functions for parameterized decorators?
- What does
@wrapspreserve? - In
@a @b def f, which runs first? - When should you use
@cachevs custom memo?
What's next#
| Topic | Page |
|---|---|
| Closures | Closures |
| Partial functions | Partial Functions |
| Class decorators | Class Decorators |
| Callable objects | Callable Objects |