Stacking Multiple Decorators

RoadmapsPython

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

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

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

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

Return to Python Roadmap