Filter concepts by levelShowing all levels.

Python · Decorators

Decorators

Wrap a function without changing how the rest of the program sees it

  • @dec above defsugar for name = dec(name), run once at def time
  • Stacking is bottom-upclosest to def applies first; outermost runs first
  • functools.wrapskeeps __name__/__doc__ pointed at the real function
  • Factories and __call__a function or a class can both produce a decorator

1.Function decorators

A decorator is a function that takes a function and returns a replacement for it. @my_decorator above def process(): is exactly process = my_decorator(process), just written before the definition instead of after.

Key features

  • @my_decorator above a def is sugar for name = my_decorator(name), run once at def time
  • A decorator takes one function argument and returns a callable to replace it
  • The replacement is usually a wrapper() closure that calls the original inside itself
  • Decoration happens once, at module load — not on every call to the decorated function
  • wrapper(*args, **kwargs) must forward every argument, and return, to work for any function

@decorator syntax and its plain-assignment equivalent

@decorator syntax and its plain-assignment equivalent
Written asRuns as
@my_decorator\ndef process(): ...def process(): ...\nprocess = my_decorator(process)
Decoration timingonce, when Python reads the def — not per call
What my_decorator receivesthe original process function object itself
What process becomeswhatever my_decorator returns — usually a wrapper
@my_decorator above def is sugar for one reassignment

def process(): ...

the original function object is built

my_decorator(process)

runs once, at def time, and returns wrapper

process = wrapper

the name process now points at wrapper instead

  1. def process(): ... — the original function object is built
  2. my_decorator(process) — runs once, at def time, and returns wrapper
  3. process = wrapper — the name process now points at wrapper instead

Command

python
def my_decorator(func):
    def wrapper(*args, **kwargs):
        # do something before
        result = func(*args, **kwargs)
        # do something after
        return result
    return wrapper

@my_decorator
def process():
    pass

@my_decorator above def is exactly name = my_decorator(name) — decoration runs once at def time, and wrapper must forward every argument and the return value.

2.Decorators with arguments

@repeat(times=3) needs three nested functions, not two: the outer one takes the decorator's own arguments (times), the middle one is the real decorator that takes func, and the inner one is wrapper, exactly like a plain decorator.

Key features

  • @repeat(times=3) calls repeat(times=3) FIRST, and decorates with whatever it returns
  • Three nested functions: outer(config args) → decorator(func) → wrapper(*args, **kwargs)
  • The outer function's parameters stay alive as closure variables inside wrapper
  • Skip the middle layer and the outer function's return value gets called as the decorator
  • Also called a decorator factory — see decorator-factories for the pattern in general

Plain decorator vs. a decorator that takes arguments

Plain decorator vs. a decorator that takes arguments
FormNestingCall at decoration time
@my_decoratordecorator(func) → wrapper(...)my_decorator(process)
@repeat(times=3)outer(times) → decorator(func) → wrapper(...)repeat(times=3) first, then decorator(process)
Three layers, called in order

repeat(times=3)

called first, returns decorator

decorator(greet)

called next, returns wrapper

greet = wrapper

times=3 stays captured inside it

  1. repeat(times=3) — called first, returns decorator
  2. decorator(greet) — called next, returns wrapper
  3. greet = wrapper — times=3 stays captured inside it

Command

python
def repeat(times):          # 1. takes the decorator's own arguments
    def decorator(func):    # 2. takes the function being decorated
        def wrapper(*args, **kwargs):   # 3. runs on every call
            return [func(*args, **kwargs) for _ in range(times)]
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    return f"Hi, {name}"

@repeat(times=3) calls repeat(times=3) first — its result must be an ordinary decorator, so a decorator with arguments needs one extra layer of nesting.

3.Method decorators

A decorator works on a method the same way it works on a plain function, as long as wrapper accepts *args, **kwargs — self simply arrives as the first positional argument, like it does for any instance method call.

Key features

  • @my_decorator above a method works exactly like above a plain function
  • wrapper(*args, **kwargs) works unchanged — self simply arrives as args[0]
  • A decorator written for plain functions rarely needs changes to also decorate methods
  • A decorator that assumes a fixed argument count (no self) breaks once applied to a method
  • Applied per-method, above each def — not once for the whole class (see class decorators)

Command

python
def log_call(method):
    def wrapper(self, *args, **kwargs):
        print(f"{type(self).__name__}.{method.__name__} called")
        return method(self, *args, **kwargs)
    return wrapper

class Account:
    @log_call
    def deposit(self, amount):
        self.balance += amount
        return self.balance

A decorator built for plain functions decorates a method unchanged, as long as wrapper forwards *args, **kwargs — self just arrives as the first one.

4.Class decorators

A class decorator takes a class instead of a function, and must return a class — usually the same one, modified. @add_repr above class Point: is exactly Point = add_repr(Point), same rule as a function decorator.

Key features

  • @my_decorator above class Name: is Name = my_decorator(Name) — same rule as functions
  • The decorator receives the class OBJECT itself, not an instance of it
  • Must return a class — usually the same cls, after adding or checking something on it
  • Runs once, when the class statement finishes — long before any instance is created
  • dataclasses.dataclass is the standard library's best-known class decorator

Command

python
def add_repr(cls):
    def __repr__(self):
        fields = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({fields})"
    cls.__repr__ = __repr__
    return cls          # must return a class

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

@my_decorator above class Name: is Name = my_decorator(Name) — the decorator receives the class object and must return a class back.

5.Stacked decorators

Stacking @bold above @italic above a def applies italic first, then bold — bottom-up, at decoration time. Calling the result then runs bold's wrapper first, which calls italic's wrapper, which calls the original — outer-in, at call time.

Key features

  • Decoration order is bottom-up: the decorator closest to def is applied FIRST
  • @bold\n@italic\ndef f(): is exactly f = bold(italic(f))
  • Call order is outer-to-inner: the outermost decorator's wrapper runs FIRST at call time
  • Swapping the stack order changes both the decoration order and the resulting behaviour
  • Each wrapper still has to forward *args, **kwargs down to the next layer

Stack order vs. what actually happens

Stack order vs. what actually happens
Written asEquivalent toRuns at call time
@bold\n@italic\ndef f(): ...f = bold(italic(f))bold\'s wrapper first, then italic\'s, then f
@italic\n@bold\ndef f(): ...f = italic(bold(f))italic\'s wrapper first, then bold\'s, then f
Decoration wraps bottom-up; calling unwraps top-down

@bold wrapper

outermost — applied LAST, runs FIRST

@italic wrapper

applied first, runs second

shout(text)

the original function, innermost

  1. @bold wrapper — outermost — applied LAST, runs FIRST
  2. @italic wrapper — applied first, runs second
  3. shout(text) — the original function, innermost

Command

python
@bold
@italic
def shout(text):
    return text.upper()

# equivalent to:
# shout = bold(italic(shout))

Stacked decorators apply bottom-up (closest to def first) but the resulting wrappers run top-down at call time — the topmost decorator sees every call first.

6.functools.wraps

Without @functools.wraps(func) on wrapper, a decorated function reports wrapper's own __name__ and __doc__ instead of the original's. @functools.wraps(func) copies them over and adds __wrapped__, pointing back at the real function.

Key features

  • Without @functools.wraps: decorated_func.__name__ == "wrapper", not the original name
  • Without @functools.wraps: decorated_func.__doc__ is wrapper's own docstring — usually None
  • functools.wraps(func) copies __name__, __doc__, __module__ and more from func onto wrapper
  • functools.wraps also sets wrapper.__wrapped__ = func, a link back to the original
  • Applied as @functools.wraps(func) directly above the wrapper def, inside the decorator

Broken vs. fixed decorator, same original function

Broken vs. fixed decorator, same original function
Attribute read on the decorated functionWithout functools.wrapsWith functools.wraps
__name__'wrapper''calculate_total'
__doc__None (wrapper has no docstring)the original function's docstring
__wrapped__AttributeError — does not existthe original, undecorated function object
@functools.wraps(func) copies identity onto wrapper

func

__name__, __doc__ — the real identity

@functools.wraps(func)

copies them onto wrapper, adds __wrapped__

wrapper

now reports func's name and docstring as its own

  1. func — __name__, __doc__ — the real identity
  2. @functools.wraps(func) — copies them onto wrapper, adds __wrapped__
  3. wrapper — now reports func's name and docstring as its own

Command

python
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@functools.wraps(func) on wrapper copies __name__/__doc__/__wrapped__ from the original — skip it and the decorated function looks like "wrapper" everywhere.

7.Callable objects

A class with __call__(self, ...) can decorate a function too — @CountCalls above def f(): runs CountCalls(f), and the resulting instance stands in for f. __call__ runs on every call, with self able to hold state as ordinary attributes.

Key features

  • __init__(self, func) receives the decorated function, usually stored as self.func
  • __call__(self, *args, **kwargs) runs on every call — where wrapper's body used to go
  • State lives as ordinary attributes on self — inspectable, unlike a closure variable
  • functools.update_wrapper(self, func) is the class-based equivalent of @functools.wraps
  • Preferred over a closure decorator when the state benefits from being directly readable

Command

python
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        return self.func(*args, **kwargs)

@CountCalls
def say_hello(name):
    return f"Hello, {name}"

A class with __call__ can decorate a function — the instance replaces it, and state lives as ordinary, readable attributes on self.

8.Decorator factories

A decorator factory is a plain function that returns a decorator instead of being one — repeat(times=3) is a call, not a decorator, but its return value is. One factory, called differently, produces many independently configured decorators.

Key features

  • A decorator factory is a plain function whose RETURN VALUE is a decorator
  • "Decorators with arguments" is the case of using a factory's output directly at a def
  • The same factory, called with different arguments, produces independent decorators
  • Each produced decorator closes over its own copy of the factory's arguments
  • The factory itself is never a decorator — it must be CALLED first to produce one

One factory, several independently configured decorators

One factory, several independently configured decorators
CallDecorator producedEnforces
validate_range(0, 100)a decorator closing over minimum=0, maximum=100value must be 0–100
validate_range(0, 5)a SEPARATE decorator closing over minimum=0, maximum=5value must be 0–5
One factory, called twice, makes two independent decorators

validate_range(minimum, maximum)

the factory — a plain function

validate_range(0, 100)

called once — returns decorator #1

validate_range(0, 5)

called again — returns a SEPARATE decorator #2

  1. validate_range(minimum, maximum) — the factory — a plain function
  2. validate_range(0, 100) — called once — returns decorator #1
  3. validate_range(0, 5) — called again — returns a SEPARATE decorator #2

Command

python
def factory(config):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # use config here
            return func(*args, **kwargs)
        return wrapper
    return decorator

configured_decorator = factory(some_config)   # call the factory first
@configured_decorator
def target():
    ...

A decorator factory is a plain function returning a decorator — call it with config first; each call produces an independent decorator.

Remember

  • @my_decorator above def process(): is exactly process = my_decorator(process) — nothing more.
  • A decorator with arguments needs three nested functions: outer(config) → decorator(func) → wrapper(...).
  • Stacked decorators apply bottom-up (closest to def first) but the topmost wrapper runs first at call time.
  • @functools.wraps(func) on wrapper is what keeps __name__, __doc__ and __wrapped__ pointed at the original.

Pro tip

  • wrapper(*args, **kwargs) must forward every argument and the return value, or the decorated function silently breaks.
  • A decorator built for plain functions decorates a method unchanged — self just arrives as the first positional argument.
  • A class decorator must return a class, usually the same cls modified — forgetting return cls replaces it with None.
  • A class with __call__ can be a decorator too — state lives as ordinary, externally-readable attributes on self.

Next up

  • Iterators and Generators
  • Exception Handling
  • Context Managers

Sources

  • Glossary — decorator: docs.python.org/3/glossary.html#term-decorator
  • functools.wraps — docs.python.org/3/library/functools.html#functools.wraps
  • Every example executed on Python 3.14.3 · checked 2026-08-21

Spotted a mistake on this page? Tell us.