Decorators

Functions that wrap other functions and add behavior to them, such as logging or permission checks. Written as @name.

On this page

Example

#
Python
import functools

def log_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}{args}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result!r}")
        return result
    return wrapper

@log_call
def award(name, exp):
    """Awards experience points."""
    return f"{name}: +{exp} EXP"

award("Emma", 15)
print(award.__name__, "|", award.__doc__)
Resultsaved result, you can check it
Calling award('Emma', 15)
award returned 'Emma: +15 EXP'
award | Awards experience points.

Definition and usage

#

A decorator is a function that takes another function and returns its extended version. Writing @log_call above a function definition is shorthand for award = log_call(award). The mechanism works because in Python, functions are ordinary objects: you can pass them as arguments and return them from other functions.

A typical decorator defines a wrapper function inside itself. It accepts any arguments through *args and **kwargs, does something before calling the original, calls it, does something after, and returns its result. This way you can add logging, timing, permission checks, or retrying, without changing the functions themselves.

The wrapper hides the original function’s name and docstring: without an extra step, award.__name__ would return "wrapper". The @functools.wraps(func) decorator applied to the wrapper copies this information, so it is worth using it every time. A decorator with its own parameters, e.g. @require_level(5), needs one more level: the outer function accepts the parameters and returns the actual decorator.

Several decorators above one function run from the bottom up: the one closest to the definition wraps it first. The standard library has many ready-made decorators, for example @property, @classmethod, @dataclass, or @functools.lru_cache.

Syntax

#
Syntax
import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return wrapper

@decorator
def function():
    ...

More examples

#
A decorator with a parameter
Python
import functools

def require_level(minimum):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(player, *args, **kwargs):
            if player["level"] < minimum:
                return f"{player['name']}: level {minimum} required"
            return func(player, *args, **kwargs)
        return wrapper
    return decorator

@require_level(5)
def enter_world(player, world):
    return f"{player['name']} enters world {world}"

print(enter_world({"name": "Emma", "level": 7}, 3))
print(enter_world({"name": "Jack", "level": 2}, 3))
Resultsaved result, you can check it
Emma enters world 3
Jack: level 5 required
The order of several decorators
Python
def bold(func):
    def wrapper():
        return f"**{func()}**"
    return wrapper

def exclaim(func):
    def wrapper():
        return f"{func()}!"
    return wrapper

@bold
@exclaim
def title():
    return "New level"

print(title())
Resultsaved result, you can check it
**New level!**

This is equivalent to title = bold(exclaim(title)), so the exclamation mark is added before the bolding.

A decorator that counts calls
Python
import functools

def count_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.calls += 1
        return func(*args, **kwargs)
    wrapper.calls = 0
    return wrapper

@count_calls
def check_answer(answer):
    return answer == "B"

for answer in ["A", "B", "C"]:
    check_answer(answer)

print(check_answer.calls)
Resultsaved result, you can check it
3

Good practices

#
  • Always add @functools.wraps(func) to the wrapper function. You keep the original’s name and docstring, which makes debugging and reading error messages easier.
  • The wrapper should accept *args, **kwargs and return the original’s result. Without return, every decorated function starts returning None.
  • The decorator’s own code runs once, when the function is defined, while the wrapper runs on every call. Put whatever needs to run on every call inside the wrapper.

Related entries

#

Spotted a mistake or missing an example? Write to us.