Generators and *args/**kwargs prepare you for decorators—functions that wrap other functions to add cross-cutting behavior: timing, authentication, caching, retry logic, and logging. Frameworks you will use daily rely on them: Flask routes, FastAPI dependencies, PyTorch’s @torch.no_grad(), and Hugging Face training callbacks.
Decorators are not syntactic sugar alone; they are how production AI services enforce policies without cluttering business logic.
Learning Objectives
By the end of this lesson, students should be able to:
- Explain that a decorator is a callable that takes a function and returns a modified function.
- Read and write the
@decoratorsyntax above function definitions. - Build a simple decorator using nested functions and
functools.wraps. - Write parameterized decorators (decorators that accept arguments).
- Identify decorators in FastAPI, Flask, and PyTorch codebases.
- Preserve function metadata (
__name__, docstrings) when wrapping.
Introduction: Wrapping Functions
A decorator applies a wrapper around a function. The @ syntax is shorthand:
@timer
def train_epoch():
...
# is equivalent to:
def train_epoch():
...
train_epoch = timer(train_epoch)
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.perf_counter() - start:.2f}s")
return result
return wrapper
Decorators in AI Frameworks
| Decorator | Framework | Purpose |
|---|---|---|
@app.post("/predict") | FastAPI / Flask | Register HTTP route handler |
@torch.no_grad() | PyTorch | Disable gradient tracking during inference |
@lru_cache | functools | Memoize expensive pure functions |
@retry(...) | tenacity / custom | Retry failed API or DB calls |
import torch
@torch.no_grad()
def predict(model, batch):
return model(batch).argmax(dim=-1)
Without this decorator, PyTorch would build a computation graph during inference, wasting memory and compute.
Parameterized Decorators
When the decorator itself needs configuration, add an outer function:
def retry(max_attempts=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
return decorator
@retry(max_attempts=5)
def call_embedding_api(text):
...
Reality: They replace the name with a wrapper. The original function still exists inside the closure, but the public binding points to wrapper. Use @wraps so tools see the original name and docstring.
Knowledge Check
- Short Answer: What does
@decoabovedef fdesugar to? Answer:f = deco(f). - True/False:
functools.wrapspreserves__name__and__doc__. Answer: True. - Multiple Choice:
@torch.no_grad()is mainly for: (a) training, (b) inference without gradients, (c) data loading, (d) plotting. Answer: (b). - Short Answer: What is a decorator? Answer: A callable that takes a function and returns a modified (wrapped) function.
- True/False: Decorators modify the original function object in place. Answer: False—they replace the name with a wrapper; the original lives in the closure.
- Short Answer: Why use
@wraps(func)? Answer: Preserve the original__name__and docstring on the wrapper. - Multiple Choice:
@app.post("/predict")is used to: (a) train CNNs, (b) register an HTTP route, (c) plot loss, (d) freeze NumPy. Answer: (b). - True/False: Parameterized decorators add an outer function that returns the actual decorator. Answer: True (e.g.
@retry(max_attempts=5)). - Short Answer: Name one stdlib decorator useful for memoizing pure functions. Answer:
@lru_cache(functools). - Multiple Choice: FastAPI/Flask route handlers are typically registered with: (a) decorators, (b) CSV writers, (c) PCA, (d) Hessian eigenvalues. Answer: (a).
Key Takeaways
- Decorators wrap functions to add reusable behavior.
@syntaxis syntactic sugar for rebinding the function name.- AI frameworks use decorators for routes, caching, retries, and gradient control.
- Next: Exception Handling for robust error paths in wrapped code.
Layered exercise: Start with a timing decorator, add logging, then add retry. Students see how decorators compose cross-cutting concerns.
Recap: Decorators wrap functions for timing, retries, routes, and gradient control; next, handle failures with Exception Handling (try/except).