深入理解 Python 装饰器:从基础到高级的完整指南
什么是装饰器?
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。装饰器的返回值通常也是一个函数对象。这种设计模式遵循了"开放封闭原则"——对扩展开放,对修改封闭。
基础装饰器的实现原理
在 Python 中,函数是一等对象,这意味着函数可以被赋值给变量、作为参数传递、作为返回值返回。装饰器正是利用了这一特性。让我们从一个简单的示例开始:
def timer_decorator(func):
"""统计函数执行时间的装饰器"""
import time
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_decorator
def calculate_sum(n):
"""计算 1 到 n 的和"""
return sum(range(n 1))
# 使用
print(calculate_sum(1000000))
# 输出: 函数 calculate_sum 执行耗时: 0.0321 秒
# 输出: 500000500000上面的代码中,@timer_decorator 语法糖等价于 calculate_sum = timer_decorator(calculate_sum)。wrapper 函数包裹了原始函数,在执行前后添加了计时功能。
带参数的装饰器
有时我们需要创建能够接受参数的装饰器。这需要三层函数结构:最外层接收装饰器参数,中间层接收被装饰函数,最内层是实际的 wrapper 函数。
def repeat(times=3):
"""重复执行了数指定次数的装饰器工厂函数"""
def decorator(func):
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator
@repeat(times=5)
def get_random_number():
"""生成随机数"""
import random
return random.randint(1, 100)
# 使用
random_numbers = get_random_number()
print(f"生成的随机数列表: {random_numbers}")这个示例展示了如何创建一个可以接受参数的装饰器。repeat(times=5) 返回一个真正的装饰器,然后这个装饰器再去包装目标函数。
保留原函数的元信息
使用装饰器后,原函数的元信息(如 __name__、__doc__ 等)会被替换成 wrapper 函数的元信息。为了解决这个问题,Python 提供了 functools.wraps 装饰器。
from functools import wraps
def logger_decorator(func):
"""带日志记录的装饰器,保留原函数元信息"""
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[调用] 函数: {func.__name__}")
print(f"[文档] {func.__doc__}")
result = func(*args, **kwargs)
print(f"[返回] 结果: {result}")
return result
return wrapper
@logger_decorator
def divide(a, b):
"""除法运算"""
return a / b
# 验证元信息被保留
print(f"函数名: {divide.__name__}") # 输出: divide
print(f"函数文档: {divide.__doc__}") # 输出: 除法运算使用 @wraps(func) 后,wrapper 函数会继承原函数的名称、文档字符串、参数注解等元信息,这对于调试和文档生成非常重要。
类装饰器
除了函数装饰器,Python 还支持类装饰器。类装饰器需要实现 __call__ 方法,使类的实例可以像函数一样被调用。
class CountCalls:
"""统计函数调用次数的类装饰器"""
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count = 1
print(f"函数 {self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def process_data(data):
"""处理数据的函数"""
return [x * 2 for x in data]
# 使用
print(process_data([1, 2, 3]))
print(process_data([4, 5, 6]))
print(process_data([7, 8, 9]))类装饰器的优势在于可以维护状态。上面的示例中,CountCalls 类实例存储了调用次数,每次调用时自动增加计数器。
装饰器堆叠
多个装饰器可以堆叠使用,执行顺序是从下到上。下面的示例展示了如何组合多个装饰器:
def validate_types(**type_hints):
"""类型验证装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 验证参数类型
for i, (arg, expected_type) in enumerate(zip(args, type_hints.values())):
if not isinstance(arg, expected_type):
raise TypeError(f"参数 {i} 应该是 {expected_type},实际是 {type(arg)}")
return func(*args, **kwargs)
return wrapper
return decorator
def cache_result(func):
"""缓存结果的装饰器"""
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = (args, frozenset(kwargs.items()))
if key not in cache:
cache[key] = func(*args, **kwargs)
return cache[key]
return wrapper
@timer_decorator
@validate_types(a=int, b=int)
@cache_result
def fibonacci(n):
"""计算斐波那契数列"""
if n <= 1:
return n
return fibonacci(n - 1) fibonacci(n - 2)
# 使用
print(f"fibonacci(35) = {fibonacci(35)}")在这个例子中,fibonacci 函数被三个装饰器包裹:最底层的 cache_result 提供缓存功能,中间的 validate_types 验证参数类型,最外层的 timer_decorator 统计执行时间。装饰器的执行顺序是从下往上,但实际函数调用时是从外往内。
实际应用场景
装饰器在 Python 开发中有广泛的应用场景:
1. 权限验证
def require_permission(permission):
"""权限验证装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
user_permissions = get_current_user_permissions()
if permission not in user_permissions:
raise PermissionError(f"缺少权限: {permission}")
return func(*args, **kwargs)
return wrapper
return decorator
def get_current_user_permissions():
"""模拟获取当前用户权限"""
return ["read", "write"]
@require_permission("admin")
def delete_user(user_id):
"""删除用户"""
return f"用户 {user_id} 已删除"2. 重试机制
def retry(max_attempts=3, delay=1):
"""失败重试装饰器"""
import time
def decorator(func):
@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
print(f"第 {attempt 1} 次尝试失败,{delay} 秒后重试...")
time.sleep(delay)
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def unstable_api_call():
"""模拟不稳定的 API 调用"""
import random
if random.random() < 0.7:
raise ConnectionError("连接失败")
return "API 调用成功"3. 性能监控
def performance_monitor(threshold=1.0):
"""性能监控装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
import time
start_time = time.time()
result = func(*args, **kwargs)
execution_time = time.time() - start_time
if execution_time > threshold:
print(f"⚠️ 性能警告: {func.__name__} 执行时间 {execution_time:.2f}s 超过阈值 {threshold}s")
return result
return wrapper
return decorator
@performance_monitor(threshold=0.5)
def heavy_computation():
"""耗时计算"""
import time
time.sleep(0.8)
return "计算完成"装饰器的最佳实践
1. 总是使用 functools.wraps:保留原函数的元信息,便于调试和文档生成。
2. 保持装饰器简洁:装饰器应该专注于单一职责,过于复杂的装饰器会降低代码可读性。
3. 合理使用文档字符串:为装饰器和 wrapper 函数添加清晰的文档说明。
4. 考虑异常处理:装饰器应该妥善处理异常,避免掩盖原始错误信息。
5. 注意装饰器的执行顺序:多个装饰器堆叠时,理解执行顺序至关重要。
总结
Python 装饰器是一个强大而优雅的特性,它让我们能够以声明式的方式为函数添加额外功能,而无需修改原函数代码。从简单的计时功能到复杂的权限验证系统,装饰器都能派上用场。掌握装饰器的高级用法,将让你的 Python 代码更加简洁、可维护和优雅。
希望本文能帮助你深入理解 Python 装饰器,并在实际项目中灵活运用这一强大工具。记住,优秀的代码不仅要有正确的功能,更要具备良好的可读性和可维护性。
