Placing two or more @ lines above a function definition creates a decorator stack. Each decorator wraps the result of the one below it, forming a chain of nested function calls. The stacking order determines two things: which decorator wraps first (bottom to top), and which decorator's wrapper executes first when the function is called (top to bottom). Getting this order wrong can break metadata, produce unexpected behavior, or crash entirely—especially when built-in descriptors like @classmethod and @staticmethod are involved.
Application Order vs. Execution Order
When Python encounters stacked decorators, it applies them from bottom to top. The decorator closest to the def statement wraps the original function first. Each subsequent decorator (moving upward) wraps the result of the previous one. The Python Language Reference states that “multiple decorators are applied in nested fashion” and gives the equivalence directly:
import functools
def decorator_a(func):
print(f" Applying A to {func.__name__}")
@functools.wraps(func)
def a_wrapper(*args, **kwargs):
print("A: before")
result = func(*args, **kwargs)
print("A: after")
return result
return a_wrapper
def decorator_b(func):
print(f" Applying B to {func.__name__}")
@functools.wraps(func)
def b_wrapper(*args, **kwargs):
print("B: before")
result = func(*args, **kwargs)
print("B: after")
return result
return b_wrapper
print("Defining greet:")
@decorator_a
@decorator_b
def greet(name):
"""Say hello."""
print(f" Hello, {name}")
return name
When Python processes this definition, the output shows bottom-to-top application:
Defining greet:
Applying B to greet # B wraps the original first
Applying A to b_wrapper # A wraps B's result
This is equivalent to writing greet = decorator_a(decorator_b(greet)). The innermost call happens first (decorator_b(greet) returns b_wrapper), then the outermost call wraps it (decorator_a(b_wrapper) returns a_wrapper).
But when the function is called, execution flows in the opposite direction—top to bottom, outermost to innermost:
greet("Ada")
# A: before <-- A's wrapper runs first (outermost)
# B: before <-- B's wrapper runs second
# Hello, Ada <-- original function runs last
# B: after <-- B's "after" code runs
# A: after <-- A's "after" code runs
Think of decorator stacking like wrapping a gift in multiple layers of paper. The first layer of paper (bottom decorator, closest to the function) goes on first. The second layer (top decorator) goes on last. But when you unwrap the gift (call the function), you peel the outer layer first and work inward.
@X
@Y
@Z
def func():
pass
def) wraps first. The correct expansion puts Z innermost:
# Step by step:
step_1 = Z(func) # Z wraps first (closest to def)
step_2 = Y(step_1) # Y wraps Z's result
step_3 = X(step_2) # X wraps Y's result
func = step_3 # equivalent to func = X(Y(Z(func)))X(Y(Z(func))). Here is a concrete way to verify it:
def X(f): print(f"X wraps {f.__name__}"); return f
def Y(f): print(f"Y wraps {f.__name__}"); return f
def Z(f): print(f"Z wraps {f.__name__}"); return f
@X
@Y
@Z
def func(): pass
# Output:
# Z wraps func
# Y wraps func
# X wraps func@ lines map directly to the nested call, top-to-bottom becoming outermost-to-innermost. X is on top so it is outermost, Z is on bottom so it is innermost:
# @X -> outermost call: X(...)
# @Y -> middle call: Y(...)
# @Z -> innermost call: Z(func)
# Result: func = X(Y(Z(func)))
# NOT Y(X(Z(func))) -- the @ order is preserved
# in the nesting, reading top-to-bottom as outer-to-inner.The @classmethod and @staticmethod Trap
@classmethod and @staticmethod are not regular function decorators. They are descriptors. A classmethod object does not have a __call__ method—it is not directly callable. It only becomes callable when Python's descriptor protocol triggers its __get__ method during attribute access on a class or instance. This distinction creates a specific trap when stacking custom decorators with @classmethod.
The Broken Order
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
class Service:
# BROKEN: custom decorator wraps classmethod object
@log_calls
@classmethod
def create(cls, name):
return cls()
# Service.create("test")
# TypeError: 'classmethod' object is not callable
Here is what happens step by step. Python applies @classmethod first (bottom to top), which wraps the create function in a classmethod descriptor object. Then Python applies @log_calls, which receives the classmethod object as its func argument. When Service.create("test") is called, log_calls's wrapper executes func(*args, **kwargs)—but func is the classmethod object, which is not callable. The descriptor protocol's __get__ was never triggered because the custom decorator simply stored the object and tried to call it directly.
The Correct Order
class Service:
# CORRECT: @classmethod wraps the custom decorator's result
@classmethod
@log_calls
def create(cls, name):
"""Create a new Service instance."""
return cls()
Service.create("test")
# Calling create
# <__main__.Service object at 0x...>
Now @log_calls wraps the raw create function first (a normal callable), producing wrapper. Then @classmethod wraps wrapper, which is also a normal callable. When Service.create("test") is called, the descriptor protocol triggers classmethod.__get__, which binds the class and returns a callable bound method. That bound method calls wrapper, which calls the original create. Everything works because @classmethod never has to wrap a non-callable descriptor.
Always place @classmethod and @staticmethod as the outermost (topmost) decorator. This is the convention established by PEP 318 when decorator syntax was introduced: @classmethod on the outside, custom decorators on the inside. The same rule applies to @staticmethod and @property. Note that @abstractmethod follows the opposite convention—it must be the innermost decorator (closest to def), placed below @classmethod or @staticmethod, as specified in the abc module documentation.
Cache.clear()?class Cache:
@staticmethod
@log_calls
def clear():
"""Flush all entries."""
return True
@log_calls wraps the raw clear function first (a normal callable), producing a wrapper. Then @staticmethod wraps that wrapper, which is also a normal callable. The descriptor protocol handles the rest at call time:
# This order works:
# 1. log_calls(clear) -> wrapper (callable)
# 2. staticmethod(wrapper) -> descriptor wrapping a callable
# At call time: descriptor.__get__() returns wrapper, which runs
# This order would BREAK:
# @log_calls
# @staticmethod <- produces a staticmethod descriptor
# def clear(): ... <- log_calls receives a non-callable descriptor@staticmethod. In this code, @staticmethod is outermost (correct position) and @log_calls is below it. So @log_calls wraps the raw function first, and @staticmethod wraps the resulting callable:
# The BROKEN order (custom decorator above @staticmethod):
@log_calls # receives staticmethod object -- NOT callable!
@staticmethod
def clear(): ...
# The CORRECT order (this quiz's code):
@staticmethod # receives log_calls' wrapper -- callable
@log_calls # receives raw clear -- callable
def clear(): ...@log_calls does fire. It wraps the function before @staticmethod does, so when Cache.clear() is called, the descriptor protocol returns log_calls' wrapper, which runs its logging code and then calls the original. You can verify:
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
class Cache:
@staticmethod
@log_calls
def clear():
return True
Cache.clear() # prints: Calling clearMetadata Propagation Through the Stack
When every decorator in a stack uses @functools.wraps(func), metadata propagates correctly through the entire chain. Each layer copies __name__, __doc__, __module__, __qualname__, __annotations__, and __type_params__ from whatever it receives (the full set defined by functools.WRAPPER_ASSIGNMENTS), and sets __wrapped__ pointing to the previous layer. The functools documentation confirms that update_wrapper automatically sets a __wrapped__ attribute on the wrapper pointing back to the original. The result is a chain of __wrapped__ references that inspect.unwrap() can follow all the way to the original function:
import functools
import inspect
def timer(func):
@functools.wraps(func)
def timer_wrapper(*args, **kwargs):
return func(*args, **kwargs)
return timer_wrapper
def logger(func):
@functools.wraps(func)
def logger_wrapper(*args, **kwargs):
return func(*args, **kwargs)
return logger_wrapper
@timer
@logger
def process(data):
"""Process incoming data."""
return data
# Metadata is correct at the outermost level:
print(process.__name__) # process
print(process.__doc__) # Process incoming data.
# The __wrapped__ chain:
print(process.__wrapped__.__name__) # process (logger's wrapper)
print(process.__wrapped__.__wrapped__.__name__) # process (original)
# inspect.unwrap follows the chain:
original = inspect.unwrap(process)
print(original is process.__wrapped__.__wrapped__) # True
If any decorator in the stack omits @functools.wraps, the chain breaks at that point. Every decorator above the broken link will copy the wrong metadata (the wrapper's name instead of the original's), and inspect.unwrap() will stop at the break because there is no __wrapped__ attribute to follow.
def broken_logger(func):
# Missing @functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@timer # has @wraps -- copies from broken_logger's wrapper
@broken_logger # missing @wraps -- chain breaks here
def process(data):
"""Process incoming data."""
return data
print(process.__name__) # wrapper -- wrong!
print(process.__doc__) # None -- lost!
@functools.wraps on layers 1 and 3 but not layer 2. What does inspect.unwrap() return?inspect.unwrap() follows the __wrapped__ chain one link at a time. If layer 2 has no __wrapped__ attribute, the traversal stops there and never reaches the original. The chain is only as strong as its weakest link:
import functools, inspect
def layer1(f):
@functools.wraps(f) # has @wraps
def w(*a, **k): return f(*a, **k)
return w
def layer2(f):
def w(*a, **k): return f(*a, **k) # NO @wraps
return w
def layer3(f):
@functools.wraps(f) # has @wraps
def w(*a, **k): return f(*a, **k)
return w
@layer1
@layer2
@layer3
def target(): """Original."""
result = inspect.unwrap(target)
# result is layer2's wrapper -- chain broke there
print(hasattr(result, '__wrapped__')) # Falseinspect.unwrap() walks __wrapped__ links one at a time. Layer 1 has __wrapped__ pointing to layer 2's wrapper. But layer 2's wrapper has no __wrapped__ attribute (because @wraps was omitted), so the traversal stops there:
# Chain visualization:
# target (layer1's wrapper)
# .__wrapped__ -> layer2's wrapper (no __wrapped__)
# STOP -- inspect.unwrap returns this
#
# layer3's wrapper and the original are unreachableValueError is only raised when inspect.unwrap() detects a cycle (a wrapper chain that loops back to itself). A missing __wrapped__ simply terminates the traversal and returns whatever it stopped on:
# ValueError only on cycles:
def bad(f):
def w(): pass
w.__wrapped__ = w # points to itself!
return w
@bad
def oops(): pass
inspect.unwrap(oops) # ValueError: wrapper loop@log_calls to see every call attempt (including retries), but retries are not being logged. Which line contains the stacking order mistake?@classmethod is a descriptor and must always be outermost, so line 1 is correct. The problem is in the relationship between @retry and @log_calls. Think about which one needs to see the other's behavior. @log_calls is inside @retry, so logging only sees the innermost call, not each retry attempt. To log every retry, @log_calls must be outside @retry: outermost decorators execute first and see everything that happens beneath them. The fix is to swap lines 2 and 3 so the stack reads @classmethod, @log_calls, @retry, @require_auth.@require_auth being close to the function is intentional -- it rejects unauthorized users before any expensive work (retry, logging) happens. The bug is between @retry and @log_calls. Which one should see the other's behavior? Debugging Stacked Decorators
When a decorator stack produces unexpected behavior, the most effective debugging technique is to inspect the __wrapped__ chain at runtime. Each layer of a properly decorated stack exposes exactly what it wraps, forming a traversable linked list from the outermost wrapper to the original function:
import inspect
def show_decorator_chain(func):
"""Walk the __wrapped__ chain and print each layer."""
layer = 0
current = func
while True:
qualname = getattr(current, '__qualname__', repr(current))
print(f" layer {layer}: {qualname}")
if not hasattr(current, '__wrapped__'):
break
current = current.__wrapped__
layer += 1
# Using the earlier example:
show_decorator_chain(process)
# layer 0: process (actually timer_wrapper, renamed by @wraps)
# layer 1: process (actually logger_wrapper, renamed by @wraps)
# layer 2: process (the original function)
# To confirm the original:
original = inspect.unwrap(process)
print(f"Original function object: {original}")
print(f"Defined at: {inspect.getfile(original)}:{inspect.getsourcelines(original)[1]}")
When a decorator chain breaks—producing the wrong name, losing a docstring, or raising TypeError—this technique immediately reveals which layer is responsible. If the names stop changing at a certain layer, that is where @functools.wraps was omitted. If the chain terminates early, a decorator is not setting __wrapped__. If a classmethod object appears in the chain, the stacking order is wrong.
For decorators with arguments (parameterized decorators), the same principle applies but with one extra nesting level. A parameterized decorator like @retry(max_attempts=3) is a factory that returns the real decorator. The factory call happens first, then the returned decorator wraps the function. In a stack, this means:
# Parameterized decorator: the outer function is a factory
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_err = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_err = e
raise last_err
return wrapper
return decorator # factory returns the real decorator
# In a stack, Python evaluates retry(3) first, gets back `decorator`,
# then applies `decorator` to the function -- same bottom-to-top rule.
@log_calls
@retry(max_attempts=3)
def fetch_data(url):
"""Fetch data from a remote API."""
pass
# Equivalent to: fetch_data = log_calls(retry(max_attempts=3)(fetch_data))
The key insight is that parameterized decorators do not change the stacking rules. The factory call resolves to a plain decorator before stacking even begins. The @functools.wraps still goes inside the innermost wrapper, and the __wrapped__ chain still works exactly as it does with non-parameterized decorators.
@retry(max_attempts=3) in a decorator stack, what happens first?@ first. Since retry(max_attempts=3) is a call expression, it executes immediately and returns a decorator function. That returned function then wraps the target:
# What Python actually does:
_decorator = retry(max_attempts=3) # factory call
fetch_data = _decorator(fetch_data) # real decorator wraps func
# The factory pattern:
def retry(max_attempts=3):
def decorator(func): # <-- this is the real decorator
@functools.wraps(func)
def wrapper(*args, **kwargs):
# retry logic using max_attempts
return func(*args, **kwargs)
return wrapper
return decorator # <-- factory returns itretry itself. retry(max_attempts=3) is a call with a keyword argument, so Python calls retry first to get back a decorator. That decorator then receives the function:
# Two-step process:
# Step 1: retry(max_attempts=3) -> returns `decorator`
# Step 2: decorator(func) -> returns `wrapper`
# NOT: retry(func, max_attempts=3)
# The parentheses after @retry trigger a call BEFORE
# the function is passed in.@ expression is evaluated eagerly at definition time. retry(max_attempts=3) runs immediately and returns a function. That function is then used as the decorator:
# Everything happens at definition time, not later:
@retry(max_attempts=3) # evaluated NOW, returns decorator
def fetch_data(url): # decorator(fetch_data) called NOW
pass
# By the time this line runs, fetch_data is already
# the fully-wrapped function.Practical Stacking Patterns
In production backend code, decorators are frequently stacked to layer cross-cutting concerns onto a single function. The order matters because it determines which checks happen first and which concerns can short-circuit the rest. Here is a common production pattern with the reasoning behind the order:
@classmethod # 1. outermost: binds to class (must be first)
@log_calls # 2. logs every call attempt, including failures
@require_role("admin")# 3. rejects unauthorized users before work begins
@cache_with_ttl(60) # 4. returns cached results, skipping retry/function
@retry(max_attempts=3)# 5. innermost custom: retries on transient failure
def create_report(cls, report_type):
"""Generate and return a business report."""
pass
This order is intentional. @classmethod goes on the outside because it is a descriptor that must wrap a callable. @log_calls runs on every call, including retries and cache hits, so it sees all activity. @require_role runs before the cache check because unauthorized users should not receive cached data. @cache_with_ttl runs before the retry logic because a cache hit should not trigger retries. @retry is innermost (closest to the function) because it should only fire when the function itself fails, not when auth or caching short-circuits the call.
Reversing any of these layers changes behavior. If @cache_with_ttl were placed outside @require_role, a cached result from an admin's previous call could be served to an unauthorized viewer. If @retry were placed outside @log_calls, the logging decorator would only see the final attempt, missing all the retries that happened before it. If @log_calls were placed inside @retry, each retry attempt would be logged individually—which might be desirable in some contexts, but changes the semantics of what "one call" means in the log output.
The general principle is: decorators that observe or filter should sit outside decorators that transform or retry. Observation decorators (logging, timing, metrics) want to see everything. Filtering decorators (auth, validation) want to reject early, before expensive work happens. Transformation decorators (caching, retry) modify the call path and should operate closest to the function where the actual work happens. This concern-priority ordering produces a stack that is predictable, auditable, and easy to reason about.
Read a decorator stack top to bottom as "when called, do this first, then this, then this." Read it bottom to top as "this wraps first, then this wraps the result." These two mental models cover every stacking scenario you will encounter.
@cache outside @require_auth. An admin calls the function and the result is cached. What happens when an unauthorized user calls the same function?@cache is outside (above) @require_auth, the cache decorator's wrapper executes first. If there is a cache hit, it returns immediately without ever calling @require_auth's wrapper. Auth is completely bypassed:
# DANGEROUS order:
@cache # executes 1st -- returns cached result
@require_auth("admin")# never reached on cache hit!
def get_secrets():
return load_secrets()
# SAFE order:
@require_auth("admin")# executes 1st -- rejects unauthorized
@cache # only reached if auth passes
def get_secrets():
return load_secrets()@cache is outermost, it checks its cache before auth ever runs. A cache hit returns the stored result to anyone, completely bypassing @require_auth:
# Execution flow with @cache outermost:
# 1. cache_wrapper("get_secrets") -> cache HIT
# 2. returns cached result immediately
# 3. require_auth's wrapper NEVER RUNS
# 4. unauthorized user gets admin's data
# Fix: put auth OUTSIDE cache:
@require_auth("admin") # runs first, rejects unauthorized
@cache # only caches for authorized users
def get_secrets():
return load_secrets()@require_auth at all. It simply checks if the result exists and returns it. Since it is outermost, it fires before auth and serves the cached result to everyone:
# Each decorator is independent -- cache knows nothing about auth.
# The only thing controlling execution order is stacking position.
# Outermost runs first. If it short-circuits, inner layers are skipped.Key Takeaways
- Application order is bottom to top; execution order is top to bottom:
@A @B def f()isf = A(B(f)). B wraps first, but A's wrapper executes first whenf()is called. - Always place
@classmethodand@staticmethodon the outside: These are descriptors, not callables. A custom decorator that wraps aclassmethodobject will crash withTypeError: 'classmethod' object is not callablebecause it bypasses the descriptor protocol. Place them outermost so they wrap a normal callable wrapper function. - Every decorator in the stack needs
@functools.wraps(func): Metadata propagates through the chain because each layer copies__name__,__doc__,__module__,__qualname__,__annotations__, and__type_params__from the previous layer's already-corrected attributes, and sets__wrapped__to point back. If any layer omits@wraps, the chain breaks and all layers above it inherit the wrong name and docstring. __wrapped__forms a linked list through the stack: Each decorator's wrapper points to the function it received.inspect.unwrap()follows this chain to the innermost original function. A broken@wrapsin any layer terminates the chain at that point.- Stack order encodes execution priority: Outermost decorators fire first and can short-circuit the rest. Place observability (logging, timing) outermost, security (auth, validation) next, optimization (caching) after that, and resilience (retry) innermost, closest to the function.
Decorator stacking is function composition. The order you choose determines what wraps what, what executes first, and what can prevent the rest from running. Getting this order right is especially critical when descriptors like @classmethod are involved, because the descriptor protocol imposes constraints that standard function-based decorators do not. Place descriptors outermost, use @functools.wraps at every layer, and read the stack top to bottom as the execution sequence. That mental model handles every stacking scenario.
Frequently Asked Questions
Python applies stacked decorators from bottom to top. The decorator closest to the def statement wraps the function first. Each subsequent decorator wraps the result of the one below it. Writing @A then @B then def func() is equivalent to func = A(B(func)).
Execution flows from top to bottom (outermost to innermost). The outermost decorator's wrapper runs first, then calls the next layer inward, and so on until the original function executes. Return values flow back outward from innermost to outermost.
A classmethod object is a descriptor, not a callable. It only becomes callable when the descriptor protocol calls its __get__ method during attribute access. When a custom decorator wraps the classmethod object directly, it tries to call it and fails because the object has no __call__ method. The fix is to always place @classmethod or @staticmethod as the outermost decorator.
Place @classmethod (or @staticmethod) on the outside, above your custom decorators. This means @classmethod wraps your decorator's wrapper function, which is a normal callable. PEP 318 established this convention when decorator syntax was introduced in Python 2.4. For a detailed comparison of when to use each, see classmethod vs staticmethod.
Yes, if every decorator in the stack uses @functools.wraps(func). Each layer copies the metadata from whatever it receives. The __wrapped__ attributes form a chain that inspect.unwrap() follows to the innermost original function. If any decorator omits @wraps, the chain breaks at that point. For a full walkthrough, see how to use functools.wraps to preserve metadata in complex stacks.
Walk the __wrapped__ chain at runtime. Each decorator that uses @functools.wraps sets a __wrapped__ attribute pointing to the function it received. Write a loop that follows __wrapped__ at each layer, printing __qualname__ to see exactly which wrappers are in the chain and where it breaks. inspect.unwrap() automates this traversal and returns the innermost original function.
No. A parameterized decorator like @retry(max_attempts=3) is a factory function that returns the real decorator. Python evaluates the factory call first, which produces a standard decorator. That decorator then participates in the normal bottom-to-top stacking process. The @functools.wraps goes inside the innermost wrapper as usual, and the __wrapped__ chain works identically.
Sources
- Python Language Reference, Section 8.7: Function Definitions — defines decorator nesting as equivalent to chained function calls.
- functools.wraps — Python Standard Library — “This function automatically adds a
__wrapped__attribute to the wrapper.” - inspect.unwrap — Python Standard Library — “It follows the chain of
__wrapped__attributes.” - PEP 318: Decorators for Functions and Methods — introduced decorator syntax in Python 2.4.
- abc.abstractmethod — Python Standard Library — documents the required
@classmethodabove@abstractmethodordering. - Python Data Model: Implementing Descriptors — defines the
__get__,__set__, and__delete__protocol that@classmethodrelies on.