You write a function to log execution time. Then another for authentication. Then caching. Soon your business logic drowns in wrapper noise. Python decorators solve this by separating cross-cutting concerns from core logic, letting you compose behavior like LEGO bricks.
What Is a Decorator?
A decorator is a callable that takes a function, adds behavior, and returns a new function. Syntactically, @decorator above a definition equals func = decorator(func). This syntactic sugar enables metaprogramming without bytecode manipulation.
Decorators with Arguments
Need configurable behavior? Add a factory layer. The outer function accepts arguments, returns the actual decorator.
Class-Based Decorators
Classes implement __call__ for stateful decorators. Useful when you need to track call counts, cache results, or hold configuration.
Stacking Multiple Decorators
Decorators apply bottom-to-top: the closest to def runs first. Order matters for logging, timing, and auth.
Real-World Patterns
| Pattern | Use Case | Stdlib Example |
|---|---|---|
| Memoization | Cache expensive calls | functools.lru_cache |
| Registration | Plugin/plugin systems | Flask route() |
| Context Injection | DB sessions, request scope | FastAPI Depends |
| Validation | Input sanitization | Pydantic validators |
"Decorators are the ultimate expression of Python's philosophy: explicit is better than implicit, but composition beats inheritance.
— Raymond Hettinger
Best Practices Checklist
- Use
functools.wrapson every wrapper - Prefer parameterized decorators (factory pattern) over global config
- Keep decorators pure: no side effects at import time
- Document the contract: what exceptions propagate, what returns change
- Type-hint with
Callable[[P], R]andParamSpecfor static analysis - Avoid deep nesting; refactor to class if state grows
✦
Open your editor. Find a function with duplicated setup/teardown logic. Extract that logic into a decorator today. Ship cleaner code tomorrow.










