Python 装饰器的高级应用:从基础到实战
装饰器是 Python 中最强大的特性之一,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。本文将深入探讨装饰器的高级用法,包括带参数的装饰器、类装饰器、装饰器链以及装饰器在缓存、日志、权限控制等场景中的实际应用。
一、装饰器器基础回顾
装饰器本质上是一个可调用的对象,它接受一个函数作为参数,并返回一个新的函数。最简单的装饰器如下:
def simple_decorator(func):
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@simple_decorator
def greet(name):
return f"你好,{name}!"
# 使用装饰器后的函数
print(greet("小豆包"))
输出结果:
调用函数: greet
你好,小豆包!
二、带参数的装饰器
有时候我们需要为装饰器传递参数。这需要创建一个装饰器工厂函数,它返回一个真正的装饰器。
示例:执行次数限制器
def max_calls(max_count):
def decorator(func):
call_count = 0
def wrapper(*args, **kwargs):
nonlocal call_count
if call_count >= max_count:
raise Exception(f"函数 {func.__name__} 调用次数已达上限 {max_count}")
call_count += 1
print(f"第 {call_count} 次调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@max_calls(3)
def expensive_operation():
return "执行耗时操作"
# 测试调用限制
print(expensive_operation()) # 第 1 次
print(expensive_operation()) # 第 2 次
print(expensive_operation()) # 第 3 次
# print(expensive_operation()) # 抛出异常
这个装饰器非常适合保护那些不应该被频繁调用的资源密集型函数,比如数据库查询、API 请求等。
三、类装饰器
装饰器不仅可以是函数,也可以是类。类装饰器通过实现 __call__ 方法使其实例可调用。
示例:函数计时器
import time
from functools import wraps
class Timer:
def __init__(self, verbose=True):
self.verbose = verbose
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
elapsed = end_time - start_time
if self.verbose:
print(f"{func.__name__} 执行时间: {elapsed:.6f} 秒")
return result
return wrapper
@Timer(verbose=True)
def fibonacci(n):
"""计算斐波那契数列"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 使用缓存优化后的版本
@Timer(verbose=True)
def fibonacci_optimized(n, memo={}):
"""带缓存的斐波那契数列"""
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci_optimized(n-1, memo) + fibonacci_optimized(n-2, memo)
return memo[n]
print(f"fibonacci(30) = {fibonacci_optimized(30)}")
四、装饰器链
多个装饰器可以叠加使用,执行顺序是从下到上(从内到外)。
示例:日志 + 认证 + 缓存
from functools import lru_cache
def log_execution(func):
"""记录函数执行的装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] 调用 {func.__name__}, 参数: args={args}, kwargs={kwargs}")
try:
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} 执行成功, 返回: {result}")
return result
except Exception as e:
print(f"[LOG] {func.__name__} 执行失败, 错误: {e}")
raise
return wrapper
def check_permission(required_level):
"""权限检查装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, user_level=None, **kwargs):
if user_level is None or user_level < required_level:
raise PermissionError(f"权限不足,需要等级 {required_level}")
return func(*args, user_level=user_level, **kwargs)
return wrapper
return decorator
@log_execution
@check_permission(required_level=3)
@lru_cache(maxsize=128)
def get_sensitive_data(data_id, user_level=None):
"""获取敏感数据的函数"""
# 模拟数据库查询
data = {
1: "机密数据 A",
2: "机密数据 B",
3: "机密数据 C"
}
return data.get(data_id, "未找到")
# 测试装饰器链
try:
print(get_sensitive_data(1, user_level=5)) # 成功
print(get_sensitive_data(2, user_level=1)) # 失败
except PermissionError as e:
print(f"错误捕获: {e}")
五、装饰器的实际应用场景
1. 简单缓存装饰器
def simple_cache(max_size=100):
"""不依赖 functools 的简单缓存实现"""
def decorator(func):
cache = {}
cache_order = []
@wraps(func)
def wrapper(*args, **kwargs):
# 创建缓存键
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
print(f"缓存命中: {func.__name__}{args}")
return cache[key]
# 缓存未命中,执行函数
result = func(*args, **kwargs)
# 添加到缓存
cache[key] = result
cache_order.append(key)
# 如果超过最大大小,删除最旧的
if len(cache_order) > max_size:
oldest_key = cache_order.pop(0)
del cache[oldest_key]
return result
# 清除缓存的方法
wrapper.clear_cache = lambda: (cache.clear(), cache_order.clear())
return wrapper
return decorator
@simple_cache(max_size=5)
def calculate_pi(precision):
"""计算圆周率的简单示例"""
print(f"计算圆周率,精度: {precision}")
# 模拟耗时计算
import math
return round(math.pi, precision)
print(calculate_pi(5)) # 第一次计算
print(calculate_pi(5)) # 从缓存读取
print(calculate_pi(10)) # 第一次计算
calculate_pi.clear_cache() # 清除缓存
2. 重试机制装饰器
import random
def retry(max_attempts=3, delay=1, backoff=2):
"""自动重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
current_delay = delay
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise Exception(f"重试 {max_attempts} 次后仍然失败: {e}")
print(f"第 {attempts} 次尝试失败: {e}")
print(f"等待 {current_delay} 秒后重试...")
time.sleep(current_delay)
current_delay *= backoff
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=1, backoff=2)
def unstable_api_call():
"""模拟不稳定的 API 调用"""
if random.random() < 0.7: # 70% 概率失败
raise ConnectionError("网络连接失败")
return "API 调用成功"
print(unstable_api_call())
3. 单例模式装饰器
def singleton(cls):
"""单例模式装饰器"""
instances = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
"""数据库连接类(单例)"""
def __init__(self):
print("创建新的数据库连接")
self.connected = True
def query(self, sql):
return f"执行查询: {sql}"
# 测试单例
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 和 db2 是同一个对象: {db1 is db2}") # True
六、最佳实践和注意事项
1. 使用 functools.wraps
from functools import wraps
def proper_decorator(func):
@wraps(func) # 保留原函数的元数据
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@wraps(func) 会复制原函数的 __name__、__doc__、__module__ 等属性到装饰器函数,这对于调试和文档生成非常重要。
2. 避免修改被装饰函数的签名
如果装饰器需要改变函数签名,应该明确说明,或者使用更高级的工具如 wrapt 库。
3. 装饰器的性能考虑
装饰器会增加函数调用的开销。在性能敏感的场景中,应该谨慎使用,或者考虑使用类装饰器来减少闭包创建的成本。
七、总结
Python 装饰器是一个非常强大的工具,它可以帮助我们:
- 减少代码重复:将横切关注点(如日志、缓存、权限)分离出来
- 提高代码可读性:通过声明式的方式添加功能
- 增强可维护性:功能修改只需调整装饰器,不影响业务逻辑
掌握装饰器的高级用法,可以让你的 Python 代码更加优雅和高效。在实际项目中,合理使用装饰器可以大幅提升代码质量和开发效率。
希望本文能够帮助你更好地理解和应用 Python 装饰器!
