1.2.1 Function Decorators
A decorator is a function that takes another function and returns a new function with extra behavior. You keep the original idea of the function, and you attach cross-cutting work (logging, timing, access checks) around it.
Python’s @ syntax is only shorthand for reassignment:
@trace
def greet(name):
return f"Hello, {name}"
means the same as:
def greet(name):
return f"Hello, {name}"
greet = trace(greet)
A minimal decorator
The inner function is the wrapper. It runs before and after the original call, then returns whatever the original returned.
from functools import wraps
def trace(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print(f"calling {fn.__name__}")
result = fn(*args, **kwargs)
print(f"{fn.__name__} -> {result}")
return result
return wrapper
@trace
def add(a, b):
return a + b
add(2, 3)
@wraps(fn) copies the original name and docstring onto the wrapper so tools and error messages stay honest.
Decorators with arguments
If you need @repeat(n=3), write one extra outer function that receives the arguments and returns the real decorator.
def repeat(n):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
value = None
for _ in range(n):
value = fn(*args, **kwargs)
return value
return wrapper
return decorator
When to use them
Use a decorator when many functions share the same around logic:
- time how long a function runs
- cache results (
functools.lru_cacheis a built-in decorator) - require a login before a view runs
- retry a network call
Do not hide important business rules inside a decorator if a reader of the function body would miss them. Keep decorators thin and obvious.