Decorators
Decorators with Arguments
A simple @retry decorator is useful, but @retry(max_attempts=3) is more flexible. Decorator factories let you configure decorator behavior with parameters, turning a single decorator into a family of related behaviors.
Class-Based Approach
An alternative using classes:
factory.py
# Decorator factory pattern
from functools import wraps
def repeat(times):
"""Decorator that repeats function execution."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# Use decorator with argument
@repeat(times=3)
def greet(name):
print(f"hello, {name}")
return "ok"
name =
greet(name)
# Decorator factory pattern
from functools import wraps
def repeat(times):
"""Decorator that repeats function execution."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# Use decorator with argument
@repeat(times=3)
def greet(name):
print(f"hello, {name}")
return "ok"
name =
greet(name)
# Decorator factory pattern
from functools import wraps
def repeat(times):
"""Decorator that repeats function execution."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# Use decorator with argument
@repeat(times=3)
def greet(name):
print(f"hello, {name}")
return "ok"
name =
greet(name)
parametrized.py
# Parametrized decorator
from functools import wraps
def log_with_prefix(prefix):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix}: calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@log_with_prefix("[INFO]")
def task_a():
return "done"
@log_with_prefix("[DEBUG]")
def task_b():
return "ok"
task_a()
task_b()
multiple_params.py
# Multiple parameters
from functools import wraps
import time
def throttle(max_calls, period_seconds):
"""Allow max_calls within period_seconds."""
def decorator(func):
calls = []
@wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
# Remove old calls
while calls and calls[0] < now - period_seconds:
calls.pop(0)
if len(calls) >= max_calls:
raise RuntimeError(f"rate limit: max {max_calls} calls per {period_seconds}s")
calls.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
@throttle(max_calls=2, period_seconds=1)
def api_call():
print("API called")
api_call()
api_call()
# Third call would raise if done quickly
optional_args.py
# Optional decorator arguments
from functools import wraps
def optional_prefix(arg=None):
"""Decorator that can be used with or without arguments."""
def decorator(func):
prefix = arg if arg is not None else "[LOG]"
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix}: {func.__name__}")
return func(*args, **kwargs)
return wrapper
# If called without parens, arg is the function itself
if callable(arg):
func = arg
arg = None
return decorator(func)
return decorator
# With argument
@optional_prefix("[WARN]")
def task_a():
pass
# Without argument
@optional_prefix
def task_b():
pass
task_a()
task_b()
class_based.py
# Class-based decorator with args
from functools import wraps
class CountCalls:
def __init__(self, max_calls):
self.max_calls = max_calls
def __call__(self, func):
count = {"value": 0}
@wraps(func)
def wrapper(*args, **kwargs):
count["value"] += 1
if count["value"] > self.max_calls:
raise RuntimeError(f"exceeded max calls: {self.max_calls}")
print(f"call {count['value']}/{self.max_calls}")
return func(*args, **kwargs)
return wrapper
@CountCalls(max_calls=3)
def task():
return "ok"
task()
task()
task()
Common Use Cases
- Repeat
ntimes - Retry up to
max_attempts - Cache with
max_size - Throttle to
max_calls_per_second - Validate with custom rules
decorator factory - a function that takes parameters and returns a decorator, enabling configurable decoration
optional decorator args - making decorator parameters optional with defaults
Exercise: practical.py
Build a rate-limiting decorator with configurable limits