5.23. Decorators

A decorator is a piece of syntax that wraps a function (or a method) in another function. The wrapper can add behaviour before and after the original call, replace the return value, or attach metadata. The shape is:

@wrapper
def f():
    ...

The @wrapper line is equivalent to f = wrapper(f) – the function f is built normally, then handed to wrapper, and the result is rebound to the name f.

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

A decorator takes a function in and returns a new function.

5.23.1. Built-in method decorators

A few decorators ship with Python and are used inside class bodies.

5.23.1.1. @property

Turns a method into a computed attribute. The caller accesses it as if it were a plain attribute (no parentheses), but a method runs on each read:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

Use it when an attribute that looks simple needs a tiny computation behind it. If the work is expensive, prefer a regular method – callers do not expect attribute reads to be slow.

5.23.1.2. @classmethod

Defines a method that receives the class as its first argument instead of an instance. The first parameter is conventionally named cls:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Class methods are the standard way to provide alternative constructors – factory functions that return an instance built in a non-default way.

5.23.1.3. @staticmethod

Defines a method that receives neither the instance nor the class – it is just a plain function that lives in the class namespace for organisational reasons:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Use it sparingly; if a function really has nothing to do with the class’s state, a plain module-level function is usually cleaner.

5.23.2. Writing a custom decorator

A decorator is a function that takes a function and returns a function. The minimum shape:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

Output:

calling add

The wrapper closes over func and forwards everything to it. *args / **kwargs lets it work on any function, not just two-argument ones. This pattern is the foundation of more elaborate decorators (timing, caching, retry-on-failure) but the core is always the same: take a function in, return a function out.