Decorators Guide in Python
What Is a Decorator?
A decorator is a function that modifies another function.
Decorators allow adding extra behavior: - logging - timing - validation - retry systems - permissions - caching
Without changing the original function.
Basic Decorator Structure
def decorator(func):
def wrapper():
print("Before function")
result = func()
print("After function")
return result
return wrapper
Applying the Decorator
@decorator
def greet():
print("Hello")
Equivalent to:
greet = decorator(greet)
Output
Before function
Hello
After function
Why Are Decorators Useful?
They separate concerns: - business logic stays clean - reusable behavior stays outside functions
Decorator With Arguments
Most decorators need *args and **kwargs.
Example
def logger(func):
def wrapper(*args, **kwargs):
print("Function called")
return func(*args, **kwargs)
return wrapper
Timing Decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Executed in {end - start:.3f}s")
return result
return wrapper
Example Usage
@timer
def slow_spell():
time.sleep(1)
Validation Decorator
def validate_power(min_power):
def decorator(func):
def wrapper(power):
if power < min_power:
return "Insufficient power"
return func(power)
return wrapper
return decorator
Example Usage
@validate_power(10)
def cast_spell(power):
return f"Spell cast with {power}"
Decorator Factories
Decorators with parameters are called:
→ Decorator Factories
Because they create decorators dynamically.
Retry Decorator
def retry(max_attempts):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception:
print("Retrying...")
return "Failed"
return wrapper
return decorator
Example Usage
@retry(3)
def unstable_spell():
raise ValueError("Boom")
Stacking Decorators
Multiple decorators can be combined.
Example
@timer
@logger
def fireball():
print("Fireball!")
Equivalent to:
fireball = timer(logger(fireball))
Execution Order
Decorators execute from bottom to top.
Why Use functools.wraps?
Without wraps, decorators replace:
- function name
- docstring
- metadata
Problem Example
def decorator(func):
def wrapper():
return func()
return wrapper
print(greet.__name__)
Output
wrapper
The original name is lost.
Correct Version With wraps
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Now metadata is preserved.
Decorators on Methods
Decorators also work on class methods.
Example
class Mage:
@staticmethod
def validate(name):
return len(name) > 2
Common Mistakes
Forgetting to Return the Wrapper
Wrong:
return func
Correct:
return wrapper
Forgetting *args and **kwargs
Wrong:
def wrapper():
Correct:
def wrapper(*args, **kwargs):
Real Use Cases
Decorators are heavily used in: - Flask - FastAPI - Django - testing frameworks - logging systems - caching systems - authentication - retry systems
Key Takeaways
- Decorators modify function behavior
- They help separate concerns
@decoratoris syntactic sugarwrapspreserves metadata- Decorators can accept parameters
- Multiple decorators can be stacked
- Decorators are extremely common in modern Python