装饰器类实现:类装饰器与可调用对象的装饰器模式
请说明如何用类来实现装饰器(类装饰器和装饰器类),对比函数式装饰器的优劣。实现一个带状态的类装饰器(如方法调用计数器@CountCalls)和一个参数化的装饰器类(如@Retry(max_retries=3, delay=1))。说明functools.wraps在类装饰器中的等价实现。
回答
孤独的心
装饰器类(通过__call__使类实例可调用):
import functools
import time
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func) # 等价于@wraps
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f'{self.func.__name__} called {self.count} times')
return self.func(*args, **kwargs)
@CountCalls
def hello(name):
return f'Hello {name}'
print(hello('Alice')) # hello called 1 times
参数化装饰器类:
class Retry:
def __init__(self, max_retries=3, delay=1):
self.max_retries = max_retries
self.delay = delay
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(self.max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == self.max_retries - 1:
raise
time.sleep(self.delay)
return wrapper
@Retry(max_retries=3, delay=0.5)
def unstable_api():
...
函数式 vs 类装饰器对比: | 特性 | 函数式装饰器 | 类装饰器 | |------|-------------|----------| | 状态管理 | 需闭包+可变对象 | 实例属性直接管理 | | 可读性 | 简洁但内层嵌套 | 更面向对象 | | 方法替换 | 返回wrapper函数 | 返回__call__ | | 参数化 | 三层嵌套(@decorator(args)) | 两层(init+call) |
functools.wraps替代:用functools.update_wrapper(self, func)将func的__name__、__doc__等复制到装饰器实例。