【问题标题】:python decorated dataclass method is shared for all instancespython 装饰的数据类方法为所有实例共享
【发布时间】:2021-10-02 14:27:09
【问题描述】:

我正在实现一个带有内部存储器的 python 装饰器(由下面的counter 表示)。

装饰器变量在dataclass 的实例之间共享,而对于公共class 的实例则不同。

为什么会这样?除了检查f 是否属于某个类以及如果是,该类是否为dataclass 之外,是否有更清洁/更简单的解决方案?

import dataclasses

def decorator(f):
    counter = {}
    def wrapper(*args, **kwargs):
        key = repr([f.__name__, args, kwargs])
        counter[key] = counter.setdefault(key, 0)+1
        result = f(*args, **kwargs)
        print(f"{counter[key]}", end=" ")
        return result
    return wrapper

@dataclasses.dataclass
class D:
    @decorator
    def foo(self):
        pass

class C:
    @decorator
    def foo(self):
        pass

尽管CD 非常相似,但下面的代码显示普通object 的实例每个都有不同的counter

>>> for i in range(5):
...     c = C()
...     c.foo()
1 1 1 1 1

当使用 dataclass 时,counter 是共享的:

>>> for i in range(5):
...     c = D()
...     c.foo()
1 2 3 4 5

【问题讨论】:

    标签: python python-3.x python-decorators python-dataclasses


    【解决方案1】:

    装饰器语法是函数应用的快捷方式,因此每次使用@decorator都是对decorator的单独调用,每个调用都会创建一个与装饰函数关联的新dict

    因此,每个装饰函数有一个计数器,并且在您的示例中,每个类都有一个装饰函数。

    但是还有另一个问题。 您的密钥取决于每个类的 __repr__ 函数,因为 *args 包括对象本身。

    对于C,未定义__repr__,因此使用object.__repr__,为每个实例生成一个唯一键。

    对于DD.__repr__ 为每个实例返回一个通用字符串'D()',因此您不会获得D 实例的唯一键。

    解决方案是在构造密钥时更加明确。也许像

    from collections import Counter
    
    
    def decorator(f):
        counter = Counter()
        def wrapper(*args, **kwargs):
            key = repr([id(f.__name__), [id(x) for x in args], [id(x) for x in kwargs.items()]])
            counter[key] += 1
            result = f(*args, **kwargs)
            # print(f"{counter[key]}", end=" ")
            return result
        return wrapper
    

    【讨论】:

    • 谢谢!但实际上,您之前的答案是必要的……确实,计数器总是共享的……这是CD 之间不同的关键。我建议你用你之前写的来补充你的答案。
    • 啊,我以为我误解了你原来的问题。
    • 单独它并不能解释问题,但我认为这是整个解释的一部分......
    • 刚刚把删除的答案折叠到这个答案的开头。
    • 你知道有没有办法让每个使用的地方都有一个独特的柜台?
    【解决方案2】:

    当您装饰的是方法而不是函数时,wrapper(*args, **kwargs) 中的 *args 的值将是一个包含隐式 self 的单元素元组。

    您的key 值将如下所示['foo', (<__main__.C object at 0x7fe8945403c8>,), {}]

    由于CD 的实例被垃圾回收,有时Python 会重用相同的内存地址,有时不会,从而导致不同的键。

    我不确定为什么 Python 会比常规类更多地重用数据类地址。

    如果您将wrapper 更改为期望self,您应该会得到一致的结果。

    def decorator(f):
        counter = {}
        def wrapper(self, *args, **kwargs):
            key = repr([f.__name__, args, kwargs])
            counter[key] = counter.setdefault(key, 0)+1
            result = f(self, *args, **kwargs)
            print(f"{counter[key]}", end=" ")
            return result
        return wrapper
    

    【讨论】:

    • 感谢您的回答,这与我的调查相符。但是,我的装饰器应该透明地用于函数、属性或方法...原始实现具有类而不是wrapper,但行为是相同的...
    • 它没有重用任何东西; D.__repr__ 根本不在其输出中使用 id。 repr(d) == 'D()' 对应D 的每个实例。
    猜你喜欢
    • 2020-02-03
    • 2021-06-24
    • 2013-06-23
    • 2010-12-12
    • 2019-02-03
    相关资源
    最近更新 更多