Python装饰器属性传递问题:如何实现多装饰器互操作性?
解决装饰器间属性/方法不互通的问题
你的问题核心在于标准的@wraps仅复制函数的基础元数据(如__name__、__doc__),不会转发内层装饰器添加的额外属性或方法。下面是两种符合Python风格的通用解决方案,无需修改第三方装饰器:
方案一:给装饰器包装函数添加属性代理
修改自定义装饰器,让包装函数通过__getattr__方法转发未定义的属性到被装饰的原始函数:
from functools import cache, wraps from typing import Callable def counted(func: Callable) -> Callable: @wraps(func) def wrapped(*args, **kwargs): wrapped.calls += 1 return func(*args, **kwargs) wrapped.calls = 0 # 代理未找到的属性到被装饰的原始函数 def __getattr__(name): return getattr(func, name) wrapped.__getattr__ = __getattr__ return wrapped @counted @cache def func_a(data): return data if __name__ == "__main__": func_a(1) func_a.clear_cache() # 现在可以正常调用cache添加的方法 print(func_a.calls) # 输出1
原理:当访问包装函数wrapped不存在的属性(如clear_cache)时,__getattr__会自动触发并从内层的cache装饰后的函数中获取对应属性/方法。
方案二:使用类装饰器实现代理
用类来实现装饰器,利用类实例的__getattr__天然支持属性转发:
from functools import cache, wraps from typing import Callable class counted: def __init__(self, func: Callable): self.func = func self.calls = 0 # 复制原始函数的元数据 wraps(func)(self) def __call__(self, *args, **kwargs): self.calls += 1 return self.func(*args, **kwargs) def __getattr__(self, name): # 转发所有未定义的属性到原始函数 return getattr(self.func, name) @counted @cache def func_a(data): return data if __name__ == "__main__": func_a(1) func_a.clear_cache() print(func_a.calls) # 输出1
这种方式更符合Python的面向对象特性,类实例的属性代理逻辑更清晰。
注意装饰器顺序的逻辑影响
- 若
@counted在@cache外层:每次调用都会触发计数,包括缓存命中的情况 - 若交换顺序(
@cache在@counted外层):仅缓存未命中时才会计数,因为缓存会直接返回结果,不会进入counted的包装逻辑
根据你的业务需求选择合适的顺序即可,两种方案都能解决属性/方法不互通的问题。
内容的提问来源于stack exchange,提问作者Poshi
相关产品推荐
相关产品推荐

