You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 22:15:52