Python decorators are one of the language’s most elegant features. Once you understand them, you’ll use them everywhere.
What is a decorator?
A decorator is a function that takes another function as input and returns a new function — typically one that adds behaviour around the original.
pythondef my_decorator(func):
def wrapper(*args, **kwargs):
print("Before the function runs")
result = func(*args, **kwargs)
print("After the function runs")
return result
return wrapper
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
# Before the function runs
# Hello, Alice!
# After the function runs
The @my_decorator syntax is just shorthand for:
pythonsay_hello = my_decorator(say_hello)
Why closures matter
The wrapper function is a closure — it captures func from the enclosing scope even after my_decorator has returned. This is what makes decorators possible.
Preserving the function signature
Without extra care, decorating a function loses its __name__ and __doc__. Use functools.wraps to preserve them:
pythonimport functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Calling", func.__name__)
return func(*args, **kwargs)
return wrapper
Decorator with arguments
To pass arguments to a decorator, add another layer:
pythondef repeat(n):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet():
print("Hello!")
greet() # Prints "Hello!" three times
Practical example: timing a function
pythonimport time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_sum(n):
return sum(range(n))
slow_sum(10_000_000)
# slow_sum took 0.2341s
Summary
- A decorator is a callable that wraps another callable.
@decoratoris syntax sugar forfunc = decorator(func).- Always use
@functools.wrapsto preserve metadata. - Decorators with arguments need three levels of nesting.