Function Decorators & @ Syntax
A decorator is a function that takes another function and returns a modified version of it. Decorators let you add behavior to existing functions without modifying their source code โ a key principle of clean, composable design.
What Is a Decorator?
At the core, decorators are just functions that wrap other functions:
The @ Syntax (Syntactic Sugar)
The @decorator_name syntax is just a cleaner way to write the pattern above. These two are identical:
Python sees @my_decorator and immediately applies it: greet = my_decorator(greet).
The Wrapper Pattern
The standard pattern for a decorator that works with any function:
Using *args and **kwargs makes the wrapper transparent โ it passes through any arguments the original function accepts.
Practical Example: Timing
Practical Example: Logging
Preserving Metadata with functools.wraps
Without @wraps, decorators destroy the original function's identity:
@wraps(func) copies __name__, __qualname__, __doc__, __annotations__, and __module__ from the original to the wrapper. Always use it.
Stacking Decorators
Multiple decorators can be applied to the same function. They are applied bottom-up (innermost first):
The innermost decorator (@italic) is applied first, then @bold wraps the result.
Decorators with Parameters
To create a decorator that accepts arguments, you need an extra level of nesting โ a function that returns a decorator:
The repeat(3) call returns a decorator, and that decorator is applied to say_hello. This is called a decorator factory.
Class Decorators
A class can also act as a decorator if it defines __call__:
Class decorators are useful when you need to store state between calls.
Built-in Decorators
Python provides several built-in decorators for class methods:
@staticmethod
A method that doesn't receive self or cls. It's just a regular function namespaced inside a class.
@classmethod
Receives the class itself as the first argument (cls). Used for alternative constructors.
@property
Turns a method into a computed attribute (covered in depth in the Properties lesson):
Decorator Use Cases in the Real World
Decorators are everywhere in Python:
- Flask/FastAPI routes:
@app.get("/users")registers URL handlers - pytest fixtures:
@pytest.fixturemarks setup functions - dataclasses:
@dataclassauto-generates__init__,__repr__, etc. - Django views:
@login_requiredenforces authentication - Caching:
@lru_cacheand@cachememoize expensive computations - Retrying:
@retry(times=3)auto-retries on failure
The decorator pattern is one of Python's most powerful and widely-used features.
Knowledge Check
When you stack decorators like `@A` above `@B` on a function, in what order are they applied?
What is the main purpose of using `@functools.wraps(func)` inside a decorator?
What does `@repeat(3)` represent if `repeat` is a decorator factory?