How to Use Nested Decorators in Python
Decorators are one of Python's most powerful features: they allow you to modify or extend the behavior of a function without changing its source code. Nested decorators (also called stacked decorators) take this a step further by applying multiple decorators to a single function, combining several modifications into a clean, reusable pattern.
This technique is widely used in web frameworks like Flask and Django (for authentication, logging, and caching), and in general Python programming for composing reusable behavior.
In this guide, you will learn how nested decorators work, understand the order of execution, and see practical examples ranging from simple to advanced.
What Is a Decorator?
A decorator is a function that takes another function as input, adds some behavior, and returns a modified version (a wrapper function):
def my_decorator(func):
def wrapper():
print("Before the function call")
func()
print("After the function call")
return wrapper
@my_decorator
def greet():
print("Hello!")
greet()
Output:
Before the function call
Hello!
After the function call
The @my_decorator syntax is equivalent to writing greet = my_decorator(greet).
Applying Multiple Decorators (Nesting)
You can stack multiple decorators on a single function by placing them above the function definition. Each decorator wraps the result of the one below it:
@decorator_a
@decorator_b
def my_function():
...
This is equivalent to:
my_function = decorator_a(decorator_b(my_function))
Execution Order: Bottom to Top
Nested decorators are applied from bottom to top: the decorator closest to the function is applied first, and the outermost decorator is applied last.
@decorator_a (applied second: outermost layer)
@decorator_b (applied first: innermost layer)
def function():
...
Basic Example: HTML Tag Wrapping
Here is a classic example that wraps a function's output with HTML tags:
def italic(func):
def wrapper():
return '<i>' + func() + '</i>'
return wrapper
def strong(func):
def wrapper():
return '<strong>' + func() + '</strong>'
return wrapper
@italic
@strong
def introduction():
return 'This is a basic program'
print(introduction())
Output:
<i><strong>This is a basic program</strong></i>
Step-by-Step Breakdown
@strongis applied first (bottom): wraps the output in<strong>...</strong>.@italicis applied second (top): wraps the already-wrapped output in<i>...</i>.
The result reads inside-out: <i><strong>This is a basic program</strong></i>.
If you reverse the decorator order:
def italic(func):
def wrapper():
return '<i>' + func() + '</i>'
return wrapper
def strong(func):
def wrapper():
return '<strong>' + func() + '</strong>'
return wrapper
@strong
@italic
def introduction():
return 'This is a basic program'
print(introduction())
Output:
<strong><i>This is a basic program</i></strong>
Now <italic> is the inner wrapper and <strong> is the outer one.
Handling Functions With Arguments
When decorating functions that accept arguments, use *args and **kwargs in the wrapper to pass them through:
def uppercase(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def exclaim(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result + "!!!"
return wrapper
@uppercase
@exclaim
def greet(name):
return f"Hello, {name}"
print(greet("Alice"))
Output:
HELLO, ALICE!!!
Order of execution:
greet("Alice")returns"Hello, Alice".@exclaimadds"!!!"->"Hello, Alice!!!".@uppercaseconverts to uppercase ->"HELLO, ALICE!!!".
Practical Example: Logging and Timing
Nested decorators shine in real-world applications where you want to compose independent concerns:
import time
from functools import wraps
def log_call(func):
"""Log when a function is called and what it returns."""
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] Calling {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} returned {result}")
return result
return wrapper
def measure_time(func):
"""Measure and print the execution time of a function."""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"[TIME] {func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@log_call
@measure_time
def compute_sum(n):
"""Compute the sum of numbers from 1 to n."""
return sum(range(1, n + 1))
result = compute_sum(1_000_000)
print(f"Result: {result}")
Output:
[LOG] Calling compute_sum((1000000,), {})
[TIME] compute_sum took 0.0209s
[LOG] compute_sum returned 500000500000
Result: 500000500000
The @measure_time decorator wraps the function first (measures execution), and @log_call wraps it second (logs the call and result, including the timing).
@functools.wrapsWithout @wraps(func), the decorated function loses its original __name__, __doc__, and other metadata:
from functools import wraps
# INCORRECT: metadata is lost without @wraps
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""Say hello."""
pass
print(hello.__name__) # 'wrapper' (not 'hello'!)
print(hello.__doc__) # None (not 'Say hello.'!)
# CORRECT: metadata is preserved with @wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""Say hello."""
pass
print(hello.__name__) # 'hello'
print(hello.__doc__) # 'Say hello.'
Output:
wrapper
None
hello
Say hello.
Practical Example: Authentication and Authorization
In web applications, nested decorators are commonly used to enforce access control:
from functools import wraps
# Simulated user
current_user = {"name": "Alice", "is_authenticated": True, "role": "admin"}
def require_auth(func):
"""Ensure the user is authenticated."""
@wraps(func)
def wrapper(*args, **kwargs):
if not current_user.get("is_authenticated"):
return "Error: Authentication required."
return func(*args, **kwargs)
return wrapper
def require_role(role):
"""Ensure the user has the required role."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if current_user.get("role") != role:
return f"Error: '{role}' role required."
return func(*args, **kwargs)
return wrapper
return decorator
@require_auth
@require_role("admin")
def delete_user(user_id):
return f"User {user_id} deleted successfully."
print(delete_user(42))
Output:
User 42 deleted successfully.
Order of checks:
@require_role("admin")checks if the user has theadminrole.@require_authchecks if the user is authenticated.
Since decorators apply bottom-to-top, but execute top-to-bottom at call time, authentication is checked first, then authorization.
Visualizing the Execution Order
Understanding nested decorator execution can be confusing. Here is a visual example:
def decorator_a(func):
def wrapper():
print("A: before")
func()
print("A: after")
return wrapper
def decorator_b(func):
def wrapper():
print("B: before")
func()
print("B: after")
return wrapper
@decorator_a
@decorator_b
def say_hello():
print("Hello!")
say_hello()
Output:
A: before
B: before
Hello!
B: after
A: after
The execution forms nested layers, like an onion:
decorator_a wrapper (outer)
-> decorator_b wrapper (inner)
-> original function
<- decorator_b wrapper returns
<- decorator_a wrapper returns
Common Mistake: Forgetting Parentheses on Parameterized Decorators
When a decorator takes arguments (like @require_role("admin")), it needs an extra layer of nesting:
# INCORRECT: decorator does not accept parameters this way
def require_role(func):
def wrapper(*args, **kwargs):
...
return wrapper
@require_role("admin") # TypeError: require_role() got an unexpected argument
def my_function():
pass
Fix
Use a decorator factory (a function that returns a decorator):
# CORRECT: decorator factory pattern
def require_role(role): # Accepts the parameter
def decorator(func): # Accepts the function
def wrapper(*args, **kwargs): # Replaces the function
# Check role here
return func(*args, **kwargs)
return wrapper
return decorator
@require_role("admin") # Works correctly
def my_function():
pass
Summary: Key Rules for Nested Decorators
| Rule | Description |
|---|---|
| Application order | Bottom to top: the decorator closest to the function is applied first |
| Execution order | Top to bottom: the outermost wrapper executes first at call time |
Use @wraps | Always use functools.wraps to preserve function metadata |
Use *args, **kwargs | Pass all arguments through wrappers to support any function signature |
| Parameterized decorators | Use a three-level nesting pattern (factory -> decorator -> wrapper) |
Conclusion
Nested decorators in Python let you compose multiple independent behaviors onto a single function in a clean, readable way.
They apply from bottom to top during decoration but execute from top to bottom at call time, forming layered wrappers around the original function.
By following best practices (using @functools.wraps, accepting *args and **kwargs, and keeping each decorator focused on a single concern), you can build powerful, reusable decorator stacks for logging, timing, authentication, caching, and much more.