2.2 python中带参数的装饰器与类装饰器
2.2 带参数的装饰器与类装饰器
- 2.2 带参数的装饰器与类装饰器
- 2.2 带参数的装饰器与类装饰器
- 带参数的装饰器:可定制的函数包装器
- 类装饰器:面向对象的装饰方式
- 带参数的类装饰器
- 装饰器组合与执行顺序
- 最佳实践与注意事项
2.2 带参数的装饰器与类装饰器
2.2 带参数的装饰器与类装饰器
在上一节中,我们掌握了基础装饰器的原理和应用。现在,让我们深入探讨两个更强大的变体:带参数的装饰器和类装饰器。它们将装饰器的灵活性和表达能力提升到了新的高度。
带参数的装饰器:可定制的函数包装器
想象一下,基础装饰器就像一个标准化的包装盒,无论什么产品都用同一种方式包装。而带参数的装饰器则像一个智能包装机——你可以通过调节参数来改变包装的材料、颜色或样式,使其适应不同的需求。
带参数的装饰器本质上是一个三层嵌套函数的结构:
- 最外层函数接收装饰器自身的参数。
- 中间层函数接收被装饰的目标函数。
- 最内层函数执行实际的包装逻辑。
让我们通过一个实际的例子来理解这个结构。假设我们需要一个装饰器,可以按指定次数重复执行一个函数,并在每次执行前打印一条日志。
def repeat(num_times, verbose=False):"""一个带参数的装饰器,用于重复执行函数。Args:num_times (int): 重复执行的次数。verbose (bool): 如果为True,则打印每次执行的日志。"""# 这是装饰器工厂:接收参数,返回一个真正的装饰器def decorator(func):# 这是真正的装饰器:接收函数,返回包装后的函数@functools.wraps(func)def wrapper(*args, **kwargs):results = []for i in range(num_times):if verbose:print(f"第 {i+1} 次执行函数 {func.__name__}")result = func(*args, **kwargs)results.append(result)return resultsreturn wrapperreturn decorator# 使用装饰器
@repeat(num_times=3, verbose=True)
def greet(name):return f"Hello, {name}!"# 测试
print(greet("Alice"))
输出:
第 1 次执行函数 greet
第 2 次执行函数 greet
第 3 次执行函数 greet
['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']
执行流程解析:
@repeat(num_times=3, verbose=True)首先调用repeat(3, True),它返回decorator函数。- 然后这个
decorator函数被应用到greet函数上,相当于执行greet = decorator(greet)。 - 最终,
greet指向了wrapper函数,这个包装器包含了重复执行和日志打印的逻辑。
带参数的装饰器在实际开发中极为有用,比如:
- 重试机制:
@retry(max_attempts=5, delay=1) - 权限验证:
@require_role('admin') - 性能监控:
@timeit(unit='ms')
类装饰器:面向对象的装饰方式
如果说函数装饰器是过程式的包装,那么类装饰器则提供了面向对象的解决方案。类装饰器通过实现 __call__ 方法,让类的实例可以像函数一样被调用。
为什么需要类装饰器?
- 状态保持:类可以更自然地维护装饰器的内部状态。
- 更清晰的代码结构:对于复杂的装饰逻辑,类的继承和多态特性可以提供更好的代码组织。
- 配置灵活性:可以通过类属性和方法来动态配置装饰行为。
让我们创建一个记录函数调用历史的类装饰器:
class CallHistory:"""类装饰器,记录函数的调用历史。"""def __init__(self, func):self.func = funcself.history = []functools.update_wrapper(self, func)def __call__(self, *args, **kwargs):# 记录调用信息call_info = {'timestamp': time.time(),'args': args,'kwargs': kwargs}self.history.append(call_info)# 执行原始函数result = self.func(*args, **kwargs)# 记录返回值call_info['result'] = resultreturn resultdef get_history(self):"""获取调用历史"""return self.historydef clear_history(self):"""清空调用历史"""self.history.clear()@CallHistory
def calculate(x, y, operation='add'):if operation == 'add':return x + yelif operation == 'multiply':return x * y# 测试
calculate(2, 3)
calculate(5, 6, operation='multiply')
calculate(10, 20)print("调用历史:")
for i, record in enumerate(calculate.get_history(), 1):print(f"{i}. 参数: {record['args']}, 操作: {record.get('kwargs', {}).get('operation', 'add')}, "f"结果: {record['result']}")
类装饰器的优势:
- 状态管理:
history列表自然地维护了函数的调用记录。 - 额外方法:我们可以提供
get_history()和clear_history()等方法来操作装饰器的状态。 - 生命周期:类实例在装饰期间一直存在,适合需要长期维护状态的场景。
带参数的类装饰器
结合前两种技术,我们可以创建带参数的类装饰器,这提供了最大的灵活性:
class RateLimit:"""带参数的类装饰器,实现函数调用频率限制。"""def __init__(self, max_calls, period):# 存储装饰器参数self.max_calls = max_callsself.period = period # 时间周期(秒)self.calls = [] # 记录调用时间def __call__(self, func):@functools.wraps(func)def wrapper(*args, **kwargs):now = time.time()# 清理过期的调用记录self.calls = [call_time for call_time in self.callsif now - call_time < self.period]# 检查是否超过限制if len(self.calls) >= self.max_calls:oldest_call = self.calls[0]wait_time = self.period - (now - oldest_call)raise RuntimeError(f"频率限制:请在 {wait_time:.2f} 秒后重试")# 记录本次调用self.calls.append(now)# 执行函数return func(*args, **kwargs)return wrapper# 使用:限制在10秒内最多调用2次
@RateLimit(max_calls=2, period=10)
def api_call(endpoint):print(f"调用 API: {endpoint}")return "success"# 测试
try:api_call("/users")api_call("/posts")api_call("/comments") # 这会触发频率限制
except RuntimeError as e:print(f"错误: {e}")
装饰器组合与执行顺序
在实际项目中,你可能会在同一个函数上应用多个装饰器。理解它们的执行顺序至关重要:
@decorator_a
@decorator_b
@decorator_c
def my_function():pass
这等价于:
my_function = decorator_a(decorator_b(decorator_c(my_function)))
执行顺序是从下往上,但包装顺序是从上往下。也就是说,decorator_c 最先执行,但 decorator_a 是最外层的包装。
最佳实践与注意事项
- 使用
functools.wraps:始终使用@functools.wraps来保留被装饰函数的元数据。 - 保持装饰器通用性:使用
*args和**kwargs来确保装饰器能处理各种函数签名。 - 考虑性能:复杂的装饰器可能引入性能开销,在性能敏感的场景要谨慎使用。
- 文档化装饰器参数:为带参数的装饰器提供清晰的文档说明。
通过掌握带参数的装饰器和类装饰器,你已经具备了构建复杂、可配置的装饰器系统的能力。这些技术在框架开发、API 设计和系统架构中有着广泛的应用,是 Python 高级编程的重要工具。
