tezvyn:

Python Decorators: Functions that Wrap Functions

AI-drafted, machine-checkedSource: docs.python.orgintermediate
Python Decorators: Functions that Wrap Functions

A decorator is a function that wraps another function, adding behavior without modifying the original code. They're used for caching, logging, or access control. The main footgun is forgetting that decorators run at definition time, not call time.

WHY IT EXISTS: Python needed a clean, reusable way to add functionality to functions or methods without permanently altering their source code. Before decorators, this often involved manually passing functions to other functions, which was verbose and less readable. Decorators provide syntactic sugar (@my_decorator) for this common pattern of wrapping functions.

THE MENTAL MODEL: Think of a decorator as a gift wrapper. It's a function that you 'wrap' around another function (the gift). When you use the gift, you're actually interacting with the wrapper first. The wrapper can do things before the original function runs (like log the arguments), run the original function, do things after it runs (like cache the result), and then return the result. The original function remains unchanged inside.

HOW IT WORKS: A decorator is a callable that returns a callable. When you write @my_decorator above def my_func():, Python translates this to my_func = my_decorator(my_func). The my_decorator function receives my_func as an argument. Inside my_decorator, you typically define a new inner function (often called wrapper) that contains the new logic plus a call to the original function. The decorator then returns this wrapper function, which replaces the original my_func in the surrounding scope.

WHEN TO USE IT: Use decorators for cross-cutting concerns that apply to many functions. Common use cases include: first, logging function entry/exit and arguments; second, caching results to avoid re-computation (like functools.lru_cache); third, enforcing access control or authentication in web frameworks like Flask or FastAPI; and fourth, timing function execution.

WHEN NOT TO USE IT: Don't use a decorator if the logic is specific to only one function; in that case, just put the logic inside the function itself. Avoid stacking too many decorators on a single function, as it can make debugging difficult by obscuring the call stack and making it hard to trace which decorator is doing what.

ONE CANONICAL EXAMPLE: A classic example is a timing decorator. You'd define a function timer that accepts another function, func, as its argument. Inside timer, you define a wrapper function. This wrapper records the start time, calls the original func, records the end time, prints the duration, and then returns the original function's result. The timer function returns this wrapper. Applying @timer to a function waste_some_time means that whenever waste_some_time is called, it's the wrapper that actually runs, automatically timing the execution. A key detail is using @functools.wraps(func) inside the decorator to preserve the original function's name and docstring.

Read the original → docs.python.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.