Learn the concept
A decorator takes a function and returns a replacement, often a wrapper that adds behavior. Python’s decorator syntax applies that transformation when the function is defined. It is useful for tracing or measurement, but hidden wrappers can make control flow harder to understand.
Use functools.wraps to preserve metadata such as the wrapped function’s name and documentation. Forward positional and keyword arguments deliberately and return the original result. A wrapper that forgets to return changes the contract.
Be cautious with retry decorators. A generic retry can duplicate side effects and can hide which failures are safe to repeat. Introduce retry policy at a layer that understands operation identity, deadlines, and idempotency. Measurement is a simpler first use of a decorator.
Run and inspect
from functools import wraps
def counted(function):
@wraps(function)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return function(*args, **kwargs)
wrapper.calls = 0
return wrapper
@counted
def add(a, b): return a + b
assert add(2, 3) == 5
assert add.calls == 1
assert add.__name__ == "add"
Your exercise
Wrap a formatting function to record calls while preserving its output and metadata. Confirm exceptions are not swallowed.
Check your understanding
The wrapper changes only the documented instrumentation behavior and does not turn failures into success.