Python装饰器实战:从入门到精通的5个实用技巧
Python装饰器(Decorator)是Python中最强大的特性之一,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。从Web框架的日志记录到API的权限验证,装饰器无处不在。本文将通过5个实用的实战案例,带你深入理解装饰器的原理与应用场景。
一、装饰器基础:理解闭函数与闭包
在深入实战之前,先理解装饰器的核心概念。装饰器本质上是一个接受函数作为参数,并返回一个新函数的函数。Python的@语法只是装饰器的语法糖,@decorator等价于func = decorator(func)。
关键在于闭包(Closure):内部函数可以访问外部函数的局部变量,即使外部函数已经返回。这就是装饰器能够在不修改原函数的情况下扩展功能的基础。
示例:简单的计时装饰器
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f'{func.__name__} 执行耗时: {end_time - start_time:.4f}秒')
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return '完成'
注意这里使用了functools.wraps,它会将原函数的__name__、__doc__等属性复制到装饰器函数中,避免元数据丢失。
二、实战技巧1:函数调用缓存(记忆化)
对于计算密集型且参数重复的函数,缓存可以极大提升性能。Python的functools.lru_cache已经提供了现成的解决方案,但自己实现一个缓存装饰器能帮你理解背后的原理。
from functools import wraps
def cache_decorator(max_size=128):
cache = {}
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 创建缓存键,注意kwargs需要有序
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
return cache[key]
result = func(*args, **kwargs)
# 简单的LRU策略:超过大小就清空
if len(cache) >= max_size:
cache.clear()
cache[key] = result
return result
return wrapper
return decorator
@cache_decorator(max_size=100)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) fibonacci(n-2)
# 测试性能
print(fibonacci(50)) # 有缓存,瞬间完成
这个缓存装饰器使用参数作为键存储结果,当参数相同时直接返回缓存值。对于递归函数如斐波那契数列,性能提升可达数百倍。
三、实战技巧2:优雅的日志记录
在生产环境中,函数调用的日志记录对于调试和监控至关重要。一个完善的日志装饰器应该记录函数名、参数、返回值和执行时间。
import logging
from functools import wraps
import time
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
def log_execution(log_level=logging.INFO, log_result=False):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
func_name = func.__name__
args_str = ', '.join([str(arg) for arg in args])
kwargs_str = ', '.join([f'{k}={v}' for k, v in kwargs.items()])
logging.log(log_level, f'调用函数: {func_name}({args_str}, {kwargs_str})')
start_time = time.time()
try:
result = func(*args, **kwargs)
exec_time = time.time() - start_time
msg = f'{func_name} 执行成功,耗时: {exec_time:.4f}秒'
if log_result:
msg = f', 返回值: {result}'
logging.log(log_level, msg)
return result
except Exception as e:
exec_time = time.time() - start_time
logging.error(f'{func_name} 执行失败,耗时: {exec_time:.4f}秒,错误: {str(e)}')
raise
return wrapper
return decorator
@log_execution(log_result=True)
def divide_numbers(a, b):
return a / b
这个日志装饰器支持配置日志级别和是否记录返回值,能捕获异常并记录错误信息,非常适合生产环境使用。
四、实战技巧3:API权限验证装饰器
在Web开发中,权限验证是常见需求。装饰器可以声明式地定义哪些函数需要特定权限,使代码更清晰。
from functools import wraps
# 模拟用户数据
class User:
def __init__(self, username, role):
self.username = username
self.role = role # 'admin', 'user', 'guest'
current_user = User('john', 'user')
def require_role(*allowed_roles):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not hasattr(current_user, 'role'):
raise PermissionError('用户未登录')
if current_user.role not in allowed_roles:
raise PermissionError(f'权限不足,需要: {allowed_roles},当前: {current_user.role}')
return func(*args, **kwargs)
return wrapper
return decorator
@require_role('admin')
def delete_user(user_id):
print(f'删除用户 {user_id}')
return '成功'
@require_role('admin', 'user')
def view_profile(user_id):
print(f'查看用户 {user_id} 的资料')
return '资料内容'
# 测试权限
try:
delete_user(123) # 会抛出权限错误
except PermissionError as e:
print(f'错误: {e}')
result = view_profile(456) # 成功执行
print(result)
这种声明式的权限控制让代码意图一目了然,也便于后续修改权限规则。
五、实战技巧4:自动重试机制
在处理网络请求或数据库操作时,操作可能因临时故障失败。自动重试装饰器可以增加系统的容错性。
import time
import random
from functools import wraps
def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts:
wait_time = delay * (backoff ** (attempt - 1))
print(f'{func.__name__} 失败 (尝试 {attempt}/{max_attempts}),{wait_time:.1f}秒后重试...')
time.sleep(wait_time)
else:
print(f'{func.__name__} 失败,已达到最大重试次数')
raise last_exception
return wrapper
return decorator
# 模拟不稳定的网络请求
@retry(max_attempts=4, delay=0.5, backoff=1.5, exceptions=(ConnectionError,))
def fetch_data(url):
success = random.random() > 0.7 # 30%成功率
if not success:
raise ConnectionError('网络连接失败')
return f'来自 {url} 的数据'
result = fetch_data('https://api.example.com/data')
print(result)
这个重试装饰器支持指数退避策略,避免短时间内频繁重试造成系统压力。
六、实战技巧5:性能分析与函数限制
有时候我们需要限制某些函数的调用频率或执行时间,防止资源被过度消耗。
import time
from functools import wraps
from collections import deque
from datetime import datetime, timedelta
def rate_limit(max_calls, time_window):
def decorator(func):
call_history = deque()
@wraps(func)
def wrapper(*args, **kwargs):
now = datetime.now()
# 清理过期记录
while call_history and call_history[0] < now - timedelta(seconds=time_window):
call_history.popleft()
if len(call_history) >= max_calls:
raise Exception(f'调用频率超限: {max_calls}次/{time_window}秒')
call_history.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
def timeout(seconds):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
import signal
def timeout_handler(signum, frame):
raise TimeoutError(f'函数执行超时: {seconds}秒')
# 设置超时信号
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
result = func(*args, **kwargs)
finally:
signal.alarm(0) # 取消超时
return result
return wrapper
return decorator
@rate_limit(max_calls=5, time_window=10)
def api_request():
print('执行API请求')
return '数据'
@timeout(seconds=2)
def long_running_task():
time.sleep(3)
return '完成'
这些装饰器可以帮助我们构建更健壮的系统,防止资源耗尽和长时间阻塞。
七、组合使用装饰器
装饰器可以组合使用,@decorator1 @decorator2会先应用decorator2,再应用decorator1。例如,我们可以为一个API函数同时添加日志、缓存和权限验证。
@require_role('admin', 'user')
@log_execution(log_result=False)
@retry(max_attempts=2, delay=0.5)
def get_user_data(user_id):
# 模拟API调用
if user_id % 3 == 0:
raise ConnectionError('服务暂时不可用')
return {'id': user_id, 'name': f'User_{user_id}'}
data = get_user_data(1)
print(data)
八、最佳实践与注意事项
1. 始终使用functools.wraps:保留原函数的元数据,这对调试和反射很重要。
2. 装饰器应该是可配置的:使用带参数的装饰器工厂函数,提高灵活性。
3. 注意装饰器的执行顺序:多个装饰器时,离函数最近的先执行。
4. 考虑性能开销:装饰器会带来额外的函数调用开销,对高频调用的函数要谨慎使用。
5. 避免过于复杂的装饰器:保持装饰器简单清晰,复杂逻辑应该拆分。
6. 文档化你的装饰器:使用docstring说明装饰器的用途和参数。
总结
Python装饰器是实现横切关注点(如日志、缓存、权限)的优雅方案。通过本文的5个实战技巧,你应该能够在实际项目中灵活运用装饰器,写出更简洁、可维护的代码。记住,装饰器的核心思想是不修改函数代码,却能增强函数功能——这正是Python编程哲学的体现。
继续学习:可以探索类装饰器、属性装饰器(property)以及装饰器在异步函数中的应用,这些将打开装饰器更多的可能性。
