snippetpythonModeratepending
Python decorators explained with practical examples
Viewed 0 times
decoratorfunctools.wrapsmemoizeretrylru_cachemetaprogramming
Problem
Need to understand and write Python decorators for logging, caching, authentication, and timing.
Solution
Python decorator patterns:
import functools
import time
# 1. Basic decorator (no arguments)
def timer(func):
@functools.wraps(func) # Preserves function metadata
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f'{func.__name__}: {elapsed:.4f}s')
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
# 2. Decorator with arguments
def retry(max_attempts=3, delay=1):
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...')
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=5, delay=2)
def flaky_api_call():
...
# 3. Memoization / caching
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
# Or use built-in:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 4. Decorator that works with/without arguments
def log(func=None, *, level='INFO'):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'[{level}] Calling {func.__name__}')
return func(*args, **kwargs)
return wrapper
if func is not None:
return decorator(func)
return decorator
@log # Works without parens
def foo(): ...
@log(level='DEBUG') # Works with parens
def bar(): ...
# 5. Class-based decorator
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)
@CountCalls
def my_func():
pass
my_func()
my_func()
print(my_func.count) # 2Why
Decorators separate cross-cutting concerns (logging, caching, auth) from business logic. functools.wraps preserves the original function's name and docstring.
Context
Python function decoration and metaprogramming
Revisions (0)
No revisions yet.