Python Decorators Guide — Writing and Understanding Decorators

Master Python decorators: function decorators, class decorators, decorator factories, and real-world use cases like caching and retry logic.

What Are Decorators?

A decorator is a function that takes another function as input, adds behavior to it, and returns a modified function. They use Python's first-class function support cleanly.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before call")
        result = func(*args, **kwargs)
        print("After call")
        return result
    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

Preserving Metadata with functools.wraps

from functools import wraps
import time

def timing(func):
    @wraps(func)  # preserves __name__, __doc__
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter()-start:.3f}s")
        return result
    return wrapper

@timing
def slow_fn():
    time.sleep(0.1)

Decorator Factories (Decorators with Arguments)

def retry(max_attempts=3, exceptions=(Exception,)):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Attempt {attempt+1} failed: {e}")
        return wrapper
    return decorator

@retry(max_attempts=3, exceptions=(ConnectionError,))
def fetch_data(url):
    ...

Real-World Use Cases

  • Caching@functools.lru_cache memoizes expensive results
  • Authentication — check JWT before executing a view
  • Rate limiting — count calls and enforce limits
  • Logging — log function calls and return values automatically
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

Frequently Asked Questions

Can decorators be applied to class methods?

Yes. Use @staticmethod, @classmethod, and @property as examples. Custom decorators work on methods, but wrap carefully to preserve self.

What is the difference between a decorator and a context manager?

Decorators modify behavior on every function call. Context managers manage resources for a single with block. Use @contextlib.contextmanager to write generators as context managers.