Python Decorators Explained: From Simple Wrappers to Production Patterns

Python Decorators Explained: From Simple Wrappers to Production Patterns
109 views

A decorator is just a function that takes a function and returns a function. Once that clicks, everything else follows — argument decorators, class decorators, stacking, and the production patterns used in every major Python framework. This guide builds from first principles to real production patterns. This guide focuses on Python decorators, with the production trade-offs senior engineers need to make sound decisions.

⚡ TL;DR: Always use @functools.wraps inside your wrapper to preserve metadata. Use decorator factories for configurable decorators. Stack decorators bottom-up. Common uses: timing, caching, retry, auth, validation.

What a decorator actually is — Python decorators

import functools

def timer(fn):
    @functools.wraps(fn)  # Preserves __name__, __doc__
    def wrapper(*args, **kwargs):
        import time
        start = time.perf_counter()
        result = fn(*args, **kwargs)
        ms = (time.perf_counter() - start) * 1000
        print(f"{fn.__name__} took {ms:.1f}ms")
        return result
    return wrapper

@timer  # Same as: greet = timer(greet)
def greet(name):
    return f"Hello, {name}"

greet("Alice")  # greet took 0.01ms

Decorator factories (decorators with arguments)

import functools, time

def retry(max_attempts=3, delay=1.0, exceptions=(Exception,)):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            last_exc = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return fn(*args, **kwargs)
                except exceptions as e:
                    last_exc = e
                    if attempt < max_attempts:
                        time.sleep(delay * attempt)
            raise last_exc
        return wrapper
    return decorator

@retry(max_attempts=5, delay=0.5, exceptions=(ConnectionError,))
def call_api(url: str) -> dict:
    pass

# Stacking (applied bottom-up):
@timer           # Second (outer)
@retry(3, 1.0)  # First (inner)
def fetch_data(url):
    pass

Built-in caching decorators

from functools import lru_cache, cache

@cache  # Python 3.9+ unbounded
def fibonacci(n):
    if n < 2: return n
    return fibonacci(n-1) + fibonacci(n-2)

@lru_cache(maxsize=128)  # Bounded LRU
def expensive_query(user_id: int, page: int):
    pass  # Result cached per (user_id, page) combo

# Class-based decorator: singleton
def singleton(cls):
    instances = {}
    @functools.wraps(cls)
    def get(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get

@singleton
class DatabasePool:
    def __init__(self): self.connections = []

Real production decorator patterns

  • ✅ @timer — log execution time for monitoring
  • ✅ @retry(attempts=3) — retry flaky external calls
  • ✅ @lru_cache — memoize expensive pure functions
  • ✅ @require_auth — validate JWT before route handler (Flask/FastAPI)
  • ✅ @rate_limit(100, 60) — throttle function calls
  • ❌ Never forget @functools.wraps — loses function metadata
  • ❌ Never use decorators for hidden side effects callers cannot see

Decorators power the Python performance optimization — @lru_cache is one of the most impactful single-line wins. External reference: Python functools documentation.

Recommended Reading

→ Designing Data-Intensive Applications — The essential book every senior developer needs.

→ The Pragmatic Programmer — Timeless engineering wisdom for writing better code.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Don’t Miss the Next Cheat Code

Join 1,000+ senior developers getting expert JS, Python, AWS and system design secrets weekly.

✓ No spam✓ Unsubscribe anytime

Related: Python asyncio vs Threading vs Multiprocessing: When to Use Each · Python Dictionary Methods and Tricks: The Complete Guide · Python Dictionary Methods and Tricks: The Complete Guide for 2025

Related: Python List Comprehension, Generators, and Itertools: The Complete Performance Guide · Python String Methods: Every str Method With Real Production Examples · Python Free-Threading: What Breaks When the GIL Becomes Optional

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.