Decorators are one of Python’s most powerful and elegant features. They allow you to modify or extend the behavior of functions and methods — without changing their actual code.
If you’ve ever used:
@app.get()in FastAPI@staticmethod@dataclass@property
…you’ve already used decorators.
This article will take you from fundamentals to practical production use.
1. Functions Are First-Class Objects
In Python, functions are objects. That means:
- You can assign them to variables
- You can pass them as arguments
- You can return them from other functions
Example:
def greet():
print("Hello")say_hi = greet
say_hi()
Output:
Hello
This ability is the foundation of decorators.
2. What Is a Decorator?
A decorator is a function that:
- Takes another function as input
- Returns a new function
- Enhances or modifies behavior
Let’s build one manually.
3. Basic Decorator Example
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
Use it like this:
def say_hello():
print("Hello")say_hello = my_decorator(say_hello)
say_hello()
Output:
Before function call
Hello
After function call
4. The @ Syntax (Syntactic Sugar)
Instead of manually wrapping:
say_hello = my_decorator(say_hello)
Python allows:
@my_decorator
def say_hello():
print("Hello")
This does the exact same thing.
The decorator syntax is just syntactic sugar.
5. Handling Function Arguments Properly
A common beginner mistake is forgetting arguments.
This will break:
def wrapper():
func()
Correct version:
from functools import wrapsdef my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper
Using *args and **kwargs ensures the decorator works with any function.
6. Why functools.wraps Is Important
Without @wraps, metadata is lost:
print(say_hello.__name__)
It would print:
wrapper
Using @wraps(func) preserves:
__name____doc__- function signature metadata
In production code, always use @wraps.
7. Practical Example: Logging Decorator
from functools import wrapsdef log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} finished")
return result
return wrapper
Usage:
@log_calls
def add(a, b):
return a + bprint(add(3, 4))
This pattern is common in:
- Logging systems
- Monitoring tools
- Debugging utilities
8. Timing Decorator (Performance Measurement)
import time
from functools import wrapsdef time_execution(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f}s")
return result
return wrapper
Used widely in:
- Performance testing
- Profiling
- Benchmarking
9. Decorators With Arguments
Sometimes decorators themselves need parameters.
Example:
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
Usage:
@repeat(3)
def greet():
print("Hello")greet()
Output:
Hello
Hello
Hello
Notice the three-layer structure:
repeat(3) → decorator → wrapper
This pattern is common in:
- Retry mechanisms
- Rate limiting
- Access control
10. How Frameworks Use Decorators
Decorators are not just academic. They power real systems.
Examples:
- Web routing
- Authentication
- Authorization
- Caching
- Metrics
- Dependency injection
When you write:
@app.get("/users")You are using a decorator that registers your function as a route handler.
Decorators make frameworks expressive and clean.
11. When to Use Decorators
Use decorators when you want to:
- Add cross-cutting concerns (logging, auth, metrics)
- Avoid repeating code
- Keep business logic clean
- Enhance behavior transparently
Avoid decorators when:
- Logic becomes deeply nested
- Debugging becomes unclear
- You are hiding critical business behavior
Clarity should always win over cleverness.
12. Key Takeaways
- Decorators modify function behavior without altering the original code
- They rely on functions being first-class objects
- Always use
functools.wraps - They are widely used in production frameworks
- Decorators improve code reuse and separation of concerns
Final Thought
Decorators are one of Python’s most elegant tools. When used properly, they:
- Reduce duplication
- Improve readability
- Enable powerful abstractions
Mastering decorators moves you from intermediate Python to advanced-level design thinking.
