Python - Decorators
Overview
Estimated time: 30–40 minutes
Decorators wrap callables to add behavior (logging, caching, validation). Learn how they work and how to build reliable decorators.
Learning Objectives
- Explain how decorators are just callables taking and returning callables.
- Write decorators that preserve metadata with
functools.wraps
. - Use common patterns: memoization (
lru_cache
), retry wrappers, access checks.
Prerequisites
- Functions and closures
Examples
from functools import wraps
def shout(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return str(result).upper()
return wrapper
@shout
def greet(name):
return f"Hello, {name}!"
print(greet("Ada"))
Expected Output:
HELLO, ADA!
Guidance & Patterns
- Explain that
@decorator
is syntax sugar forfn = decorator(fn)
. - Demonstrate parameterized decorators (
@decorator(arg)
) that return a decorator. - Show how missing
@wraps
breaks__name__
/__doc__
and tooling.
Best Practices
- Prefer composition over complex, stateful decorators. Keep wrappers thin and testable.
- For caching, prefer
functools.lru_cache
over homegrown caches; document invalidation strategy. - Mind async functions: use
async def
wrappers and consideranyio
for structured concurrency.
Exercises
- Implement a
@retry(n=3)
decorator with exponential backoff for exceptions. - Write a decorator that validates function arguments using type hints at runtime.