E.B.1 Advanced Decorator Usage


A decorator wraps a function with reusable outer behavior. Use it when many functions need the same logic, such as logging, timing, retry, permission checks, or tracing.
What You Need
- Python 3.10+
- No external packages
- Basic understanding of functions
Key Terms
- Wrapper: the inner function that runs before and after the original function.
- Cross-cutting logic: logic needed in many places but not part of the business task itself.
functools.wraps: keeps the original function name and metadata after decoration.- Decorator order: the top decorator runs first when the function is called.
Run A Logging And Retry Decorator
Create decorator_demo.py:
from functools import wraps
def log_call(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print(f"[LOG] start {fn.__name__}")
result = fn(*args, **kwargs)
print(f"[LOG] end {fn.__name__}")
return result
return wrapper
def retry(max_retries=2):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, max_retries + 2):
try:
return fn(*args, **kwargs)
except RuntimeError as error:
last_error = error
print(f"[RETRY] attempt={attempt} error={error}")
raise last_error
return wrapper
return decorator
state = {"attempt": 0}
@log_call
@retry(max_retries=2)
def fetch_model_info(model_id):
state["attempt"] += 1
if state["attempt"] < 2:
raise RuntimeError("temporary network error")
return {"model_id": model_id, "status": "ready"}
print(fetch_model_info("demo-v1"))
print(fetch_model_info.__name__)
Run it:
python decorator_demo.py
Expected output:
[LOG] start fetch_model_info
[RETRY] attempt=1 error=temporary network error
[LOG] end fetch_model_info
{'model_id': 'demo-v1', 'status': 'ready'}
fetch_model_info
This shows three useful details: the business function stays short, retry behavior is centralized, and wraps preserves the function name.
Change The Order
Swap the decorators:
@retry(max_retries=2)
@log_call
def fetch_model_info(model_id):
Now logging runs inside each retry attempt. This is why decorator order matters in service code.
When To Use Decorators
Use decorators for:
- Logging and tracing
- Timing
- Retry around unstable I/O
- Permission checks
- Framework registration
Avoid decorators when the wrapper hides important business logic or when one function already has too many layers.
Common Mistakes
- Forgetting
@wraps, then logs and frameworks see every function aswrapper. - Retrying every exception, including validation or permission errors that should fail immediately.
- Stacking many decorators until the execution order is hard to debug.
Practice
Add a require_role("admin") decorator before fetch_model_info. Make it raise PermissionError for non-admin users, and do not retry permission errors.