HomeGuidesPythonPython Decorators Explained — @syntax, functools.wraps & Stacking
🐍 Python

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.

Examifyr·2026·6 min read

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)
Note: `@shout` above `def greet` is exactly the same as `greet = shout(greet)` after the definition.

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 times

Stacking 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>
Note: Application order: bottom decorator wraps first. So @italic wraps text(), then @bold wraps that result.

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

← Previous
Python List Comprehensions Explained — Syntax & Performance
Next →
Python Generators Explained — yield, next() & Memory Efficiency
← All Python guides