patternpythonModeratepending
Pattern: Decorator pattern for extending behavior
Viewed 0 times
decoratorwrapperfunctools.wrapsretryloggingcache
Problem
Need to add behavior to existing objects or functions without modifying their source code or using inheritance.
Solution
Wrap the original to add behavior:
# Python decorators (language feature):
import functools, time, logging
def retry(max_attempts=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
time.sleep(delay * (2 ** attempt))
return wrapper
return decorator
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f'Calling {func.__name__}({args}, {kwargs})')
result = func(*args, **kwargs)
logging.info(f'{func.__name__} returned {result}')
return result
return wrapper
def cache_result(ttl_seconds=60):
def decorator(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
now = time.time()
if args in cache and now - cache[args][1] < ttl_seconds:
return cache[args][0]
result = func(*args)
cache[args] = (result, now)
return result
return wrapper
return decorator
# Stack decorators:
@retry(max_attempts=3)
@log_calls
@cache_result(ttl_seconds=300)
def fetch_user(user_id):
return api.get(f'/users/{user_id}')
# Python decorators (language feature):
import functools, time, logging
def retry(max_attempts=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
time.sleep(delay * (2 ** attempt))
return wrapper
return decorator
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f'Calling {func.__name__}({args}, {kwargs})')
result = func(*args, **kwargs)
logging.info(f'{func.__name__} returned {result}')
return result
return wrapper
def cache_result(ttl_seconds=60):
def decorator(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
now = time.time()
if args in cache and now - cache[args][1] < ttl_seconds:
return cache[args][0]
result = func(*args)
cache[args] = (result, now)
return result
return wrapper
return decorator
# Stack decorators:
@retry(max_attempts=3)
@log_calls
@cache_result(ttl_seconds=300)
def fetch_user(user_id):
return api.get(f'/users/{user_id}')
Why
Decorators add cross-cutting concerns (logging, caching, retry) without modifying business logic. They compose naturally.
Revisions (0)
No revisions yet.