Decorators in Python
Decorators wrap functions or methods to add behavior without changing call sites.
Basics (@ syntax)
A decorator is a callable that takes a function and returns a function.
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"→ {func.__name__}{args, kwargs}")
out = func(*args, **kwargs)
print(f"← {func.__name__} -> {out!r}")
return out
return wrapper
@log_calls
def add(a, b):
return a + b
Preserve metadata (functools.wraps)
Use @wraps to copy __name__, __doc__, annotations, etc.
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*a, **k):
print(func.__name__)
return func(*a, **k)
return wrapper
Decorators with arguments
Create a decorator factory that returns the real decorator.
def retry(times=3):
def decorator(func):
@wraps(func)
def wrapper(*a, **k):
last = None
for _ in range(times):
try:
return func(*a, **k)
except Exception as e:
last = e
raise last
return wrapper
return decorator
@retry(times=5)
def flaky(): ...
Stacking decorators
Applied top‑down, executed inside‑out.
@cache
@log
def compute(x): ...
Method decorators
Work the same on methods; first parameter is self for instance methods.
Common built‑ins
functools.lru_cache(maxsize=128)— memoizationfunctools.cache(3.9+) — unbounded cachefunctools.singledispatch— function overloading by first arg type
Class decorators
Decorators can modify or replace classes.
def add_repr(cls):
cls.__repr__ = lambda self: f"{cls.__name__}({self.__dict__})"
return cls
@add_repr
class Point: ...
State and configuration
Store config on the wrapper or close over state; prefer pure wrappers where possible.
Testing and import‑time effects
Decorators run at import time. Keep side effects minimal and deterministic.
Summary
- Use
@wrapsto preserve metadata - For parameters, write a decorator factory
- Stack carefully; prefer small, focused decorators; lean on
functoolsbuilt‑ins