Python decorators — how they work and how to write one
decorator syntax, wrapper function pattern, functools.wraps, decorator with arguments, stacking decorators, practical examples
Decorators
A decorator is a function that takes a function and returns a modified version. The @decorator syntax is shorthand for func = decorator(func).
def log(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Done")
return result
return wrapper
@log
def add(a, b):
return a + b
print(add(2, 3))
# Calling add
# Done
# 5
Preserving Metadata
from functools import wraps
def log(func):
@wraps(func) # preserves __name__, __doc__
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
Decorator with Arguments
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say(msg):
print(msg)
say("hello") # prints hello 3 times
Always use @wraps(func) inside your wrapper to preserve the original function's identity.
Decorators are one of Python's most powerful metaprogramming features. The standard library uses them everywhere: @property, @classmethod, @staticmethod, @functools.lru_cache, and @dataclass are all decorators. Web frameworks like Flask and FastAPI use decorators to register route handlers: @app.route("/"). Once you understand that a decorator is just a function that receives and returns a function, the pattern becomes natural. Stacking multiple decorators is valid — they apply bottom-up, so the decorator closest to the function runs first.
