Decorators are Python's most elegant metaprogramming feature. The @ syntax lets you wrap any callable to inject behavior — logging, caching, access control, retries — without touching the original function's code. Under the hood, @decorator is just syntactic sugar for func = decorator(func), but that one-line abstraction powers everything from Flask routes to dataclasses to pytest fixtures.
Master python decorators
Take the Python Fundamentals course with hands-on lessons and challenges.
Uses try/finally so the elapsed time is logged even if the function raises. Logging instead of print means this works in production without cluttering stdout. The perf_counter clock has nanosecond resolution.
The func=None sentinel pattern makes the decorator flexible: @retry works without parentheses, and @retry(max_attempts=5) works with them. The exponential backoff multiplies the delay after each failure. Filtering on specific exception types prevents retrying on bugs like TypeError.
Builds on the same idea as functools.lru_cache but adds time-based expiration. The cache key is built from positional and keyword arguments. Attaching cache_clear to the wrapper gives callers a way to invalidate stale data — the same pattern lru_cache uses. For production, consider functools.lru_cache or cachetools instead of rolling your own.
A parametrized decorator that checks for a JWT token and optionally enforces a role. Notice the stacking order: @app.route must come before @require_auth so Flask registers the wrapped function. The decorator sets g.current_user, making the authenticated user available downstream without polluting function signatures.
Uses a deque as a sliding window of call timestamps. Old entries are evicted on each call, and if the window is full, the decorator raises instead of silently blocking. In production you would swap the deque for a Redis-backed counter, but the decorator interface stays identical.
A class-based decorator that uses inspect.signature to bind arguments and check them against type annotations. Using functools.update_wrapper (the class equivalent of @functools.wraps) preserves the original function's metadata. This pattern is how libraries like pydantic and beartype work under the hood.
Forgetting `@functools.wraps(func)` — the decorated function loses its name, docstring, and type annotations, which breaks introspection tools, API docs generators, and debugging
Always apply `@functools.wraps(func)` to your inner wrapper function. It copies `__name__`, `__doc__`, `__annotations__`, `__module__`, and sets `__wrapped__` so you can access the original function.
Forgetting to return the result of the wrapped function — writing `func(*args, **kwargs)` without `return`, so the decorated function silently returns `None`
Always write `result = func(*args, **kwargs)` then `return result` (or just `return func(*args, **kwargs)` directly). This is the most common decorator bug and it produces no error — just wrong values downstream.
Getting the stacking order wrong — placing `@app.route` below `@require_auth` so Flask registers the unwrapped function and auth is never checked
Remember that decorators apply bottom-up. The outermost decorator (top of the stack) wraps everything below it. For Flask: `@app.route` on top, then `@require_auth` below, so the route handler is the authenticated version.
Using mutable default arguments in the decorator factory — e.g. `def decorator(func, cache={})` — which shares state across all decorated functions
Create mutable state (dicts, lists, deques) inside the decorator or wrapper scope, not as default arguments. Each decorated function should get its own isolated state unless sharing is intentional.
Decorators are functions that wrap other functions to inject cross-cutting behavior — timing, caching, auth, retries, validation — without modifying the original code. The `@` syntax is sugar for reassignment. Always use `functools.wraps` to preserve metadata. Parametrized decorators use a three-level nesting pattern (factory, decorator, wrapper). Class-based decorators store state between calls. Stacking applies bottom-up. The best decorators are thin, composable, and transparent to type checkers.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.