python 所有实例共享修饰的数据类方法

python decorated dataclass method is shared for all instances

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

装饰器变量在 dataclass 实例之间共享,而对于公共 class.

实例则不同

为什么会这样?除了检查 f 是否属于 class 以及如果 class 是否属于 dataclass 之外,还有 cleaner/easier 解决方案吗?

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

当您装饰方法而不是函数时,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

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

因此每个修饰函数一个计数器,在您的示例中每个 class 一个修饰函数。

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

对于 C__repr__ 未定义,因此使用 object.__repr__,为每个实例生成一个唯一的密钥。

对于 DD.__repr__ returns 每个实例的通用字符串 '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