Decorators
Python · Core Language · Run with python3
Explanation
A decorator is a callable that takes a function and returns a (usually modified) function. The `@decorator` syntax is syntactic sugar for `func = decorator(func)`. Decorators are used for cross-cutting concerns: logging, timing, access control, caching, retry logic, and input validation — without modifying the decorated function's body. `functools.wraps` preserves the wrapped function's metadata (`__name__`, `__doc__`).
Real-World Scenario
A web framework registers URL routes with `@app.route("/users")`. An internal API enforces authentication with `@require_auth`. A data pipeline retries flaky network calls with `@retry(max_attempts=3)`. All these are decorators — the business logic is untouched, and behaviour is composed. ```python import functools import time def timer(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{func.__name__} took {elapsed:.4f}s") return result return wrapper def retry(max_attempts=3): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts: raise print(f"Attempt {attempt} failed: {e}. Retrying...") return wrapper return decorator @timer @retry(max_attempts=3) def fetch_data(url: str) -> str: return f"data from {url}" print(fetch_data("https://example.com")) ```
Quick Snippet
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
print(f"Attempt {attempt} failed: {e}. Retrying...")
return wrapper
return decorator
@timer
@retry(max_attempts=3)
def fetch_data(url: str) -> str:
return f"data from {url}"
print(fetch_data("https://example.com"))Full Example
# Compile: python3 decorators.py
# Output:
# fetch_data took
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
print(f"Attempt {attempt} failed: {e}. Retrying...")
return wrapper
return decorator
@timer
@retry(max_attempts=3)
def fetch_data(url: str) -> str:
return f"data from {url}"
result = fetch_data("https://example.com")
print(result[:20])
# Verify @functools.wraps preserves metadata
print(fetch_data.__name__) # fetch_data (not 'wrapper')
python3 decorators.py