You've seen @property, @staticmethod, and @lru_cache scattered across Python codebases. Maybe you've even copied a retry decorator from Stack Overflow. But if you treat decorators as magic syntax rather than callable objects, you're leaving one of Python's most expressive metaprogramming tools on the table.
What a Decorator Actually Is
A decorator is a callable that takes a callable and returns a callable. That's it. The @ syntax is syntactic sugar:
The original function gets replaced by whatever the decorator returns. This simple mechanism powers cross-cutting concerns: logging, timing, authentication, caching, retries, and more.
Preserve Metadata with <code>functools.wraps</code>
Without wraps, your decorated function loses its name, docstring, and annotations. This breaks introspection, debugging, and tools like Sphinx or FastAPI's automatic docs.
Decorators That Accept Arguments
Real-world decorators often need configuration. Add a factory layer:
Class Decorators and Method Decorators
Decorators work on classes too. A class decorator receives the class object and returns a new class (or the same one modified).
For methods, the same rules apply. But watch self binding: the wrapper receives the instance as the first argument automatically.
Stacking Decorators
Multiple decorators apply bottom-to-top (closest to the function first). Order matters.
Built-in Decorators You Should Know
| Decorator | Module | Purpose |
|---|---|---|
| @property | builtins | Managed attribute access |
| @classmethod | builtins | Class-bound method |
| @staticmethod | builtins | Namespace-bound method |
| @lru_cache | functools | Memoization |
| @cached_property | functools | Per-instance cached property |
| @dataclass | dataclasses | Auto-generate __init__, __repr__, etc. |
| @contextmanager | contextlib | Generator-based context manager |
| @singledispatch | functools | Function overloading by type |
Real-World Pattern: Rate Limiting
Best Practices Checklist
- Use
@wrapson every wrapper. - Accept
*args, **kwargsand pass them through unchanged. - Return the wrapped function's result.
- Handle exceptions explicitly; don't swallow them unless intended.
- Keep decorators focused: one concern per decorator.
- Prefer composition over monolithic decorators.
- Document the decorator's behavior and parameters.
- Test the decorated function, not just the wrapper.
✦
Decorators aren't syntax tricks. They're first-class functions that let you separate cross-cutting logic from business logic. Start small: wrap a function with timing, add @wraps, then parameterize it. Next time you copy-paste logging boilerplate across ten functions, write a decorator instead. Your future self will thank you.










