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
尽管 C
和 D
非常相似,下面的代码显示正常 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>,), {}]
。
由于 C
和 D
的那些实例被垃圾收集,有时 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__
,为每个实例生成一个唯一的密钥。
对于 D
、D.__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
我正在实现一个带有内部存储器的 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
尽管 C
和 D
非常相似,下面的代码显示正常 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>,), {}]
。
由于 C
和 D
的那些实例被垃圾收集,有时 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__
,为每个实例生成一个唯一的密钥。
对于 D
、D.__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