Python Decorators: How They Work & What Exams Test
Decorators look like magic but they're just functions that wrap other functions. Here's everything exams ask — including the stacking order trap.
What is a decorator?
A decorator is a function that takes another function as input and returns a modified version of it. The `@` syntax is shorthand for passing a function as an argument.
def shout(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return str(result).upper()
return wrapper
@shout
def greet(name):
return f"hello, {name}"
print(greet("alice")) # "HELLO, ALICE"
# Equivalent without @ syntax:
greet = shout(greet)Preserving metadata with functools.wraps
Without `functools.wraps`, the decorated function loses its original `__name__` and `__doc__`. Always use `@wraps(func)` inside your decorator.
from functools import wraps
def my_decorator(func):
@wraps(func) # preserves __name__, __doc__, etc.
def wrapper(*args, **kwargs):
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper
@my_decorator
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'add' (without @wraps: 'wrapper')
print(add.__doc__) # 'Add two numbers.'Decorator with arguments
To pass arguments to a decorator, add another layer of nesting — a factory function that returns the actual decorator.
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi() # prints "Hi!" three timesStacking decorators
When you stack multiple decorators, they apply bottom-up (closest to the function first), but execute top-down when the function is called.
def bold(func):
@wraps(func)
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@bold # applied second
@italic # applied first
def text():
return "Hello"
print(text()) # <b><i>Hello</i></b>Built-in decorators: @property, @staticmethod, @classmethod
Python ships three essential built-in decorators used in class definitions.
class Circle:
def __init__(self, radius):
self._radius = radius
@property # getter — access like an attribute
def area(self):
return 3.14159 * self._radius ** 2
@staticmethod # no self or cls — independent utility
def unit_circle():
return Circle(1)
@classmethod # receives the class (cls) not the instance
def from_diameter(cls, d):
return cls(d / 2)
c = Circle(5)
print(c.area) # 78.53... (no parentheses needed)
c2 = Circle.from_diameter(10)Exam tip
Stacking order is the top exam trap: decorators apply bottom-up but execute top-down. Also: without `functools.wraps`, `func.__name__` returns the wrapper's name — exams test this directly.
Think you're ready? Prove it.
Take the free Python readiness test. Get a score, topic breakdown, and your exact weak areas.
Take the free Python test →Free · No sign-up · Instant results