Stacking Multiple Decorators
Overview
When you apply multiple decorators to a function (stacking them with `@`), they are applied bottom-to-top. The original function is passed to the bottom decorator, the result of that is passed to the decorator above it, and so on. Think of it like wrapping a gift: the first box (bottom decorator) wraps the item directly, and the next box (top decorator) wraps the inner box. @top_decorator @bottom_decorator def my_func(): pass This is exactly equivalent to manually composing them as `my_func = top_decorator(bottom_decorator(my_func))`. @app.route('/api') @require_auth def get_data(): pass **The Missing Metadata Trap**: If `@require_auth` forgets to use `@functools.wraps`, `@app.route` will register the endpoint with the name `wrapper` instead of `get_data`. If multiple routes do this, your web framework will crash with duplicate route names.
It allows composing small, reusable behaviors—like adding both authentication and logging to a single web route without writing a single monolithic decorator.
Where used: FastAPI routes, Flask views, Class method definitions
Why learn this
- You will often need to apply multiple middleware-like functions to a single endpoint in web frameworks.
- It helps debug which decorator is breaking your function's signature or return value.
Code walkthrough
def strong(func):
def wrapper():
return f'<strong>{func()}</strong>'
return wrapper
def emphasize(func):
def wrapper():
return f'<em>{func()}</em>'
return wrapper
@strong
@emphasize
def render_text():
return 'Warning'
print(render_text())
Focus: The output is <strong><em>Warning</em></strong> because `@emphasize` is applied first (wrapping 'Warning'), then `@strong` wraps the result.
Aha moment
def log_request(func):
print('Applying logger')
return func
def auth_check(func):
print('Applying auth')
return func
@log_request
@auth_check
def get_user_data():
pass
Prediction: What order are the 'Applying' messages printed when this script runs?
Common guess: 'Applying logger' then 'Applying auth'
Decorators are applied bottom-to-top at definition time. The interpreter first passes `get_user_data` to `auth_check()`, printing 'Applying auth', and then passes that result to `log_request()`, printing 'Applying logger'.
Common mistakes
- Order of execution confusion: Decorators are applied bottom-to-top, meaning the bottommost decorator wraps the `def` function first. @logger @timer def fetch(): pass In this example, `@timer` wraps `fetch` directly, and `@logger` wraps the resulting function.
- Losing function metadata: If any decorator in the stack forgets to use `@functools.wraps`, the final returned function will have the wrong `__name__` and `__doc__`.
Glossary
- middleware-like
- Software components that sit between an application's core logic and incoming requests, usually to handle tasks like security or logging. Example: `@require_auth`.
- monolithic
- A large, single block of code or software that tries to handle all features and functionality internally, making it hard to change or reuse. Example: `@auth_and_log_and_cache`.
Recall questions
- In what order are stacked decorators applied to a function?
- Why would you stack decorators instead of writing one large decorator?
- What happens if a decorator in the middle of a stack forgets to use `@functools.wraps`?
Understanding checks
What is the output of this stacked decorator code?
<b><i>hi</i></b>
The decorators are applied bottom-to-top, so `@italic` wraps `greet` first resulting in `<i>hi</i>`, and then `@bold` wraps that result, putting `<b>` on the outside.
Why might a function lose its original name when multiple decorators are stacked?
Because the decorators return wrapper functions, masking the original function's metadata.
Unless every decorator in the stack uses `@functools.wraps`, the final returned function will just be the `wrapper` from the topmost decorator, losing the original `__name__` and `__doc__`.
Practice tasks
Stacking formatters
Apply two decorators to the `get_message` function: `@strip_whitespace` (closest to the function) and `@make_uppercase` (on top). The decorators are already defined. The final output for ' error ' should be 'ERROR'.
Challenge
Authentication and Timing
You have a `fetch_data` function. Apply `@require_auth` (defined first) and then `@timer` (defined second) to it. Ensure `require_auth` is closest to the function so we only time the function if authentication succeeds.
Continue learning
Previous: Function Decorators