简体   繁体   English

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

[英]python decorated dataclass method is shared for all instances

I'm implementing a python decorator with an internal memory (represented by counter below).我正在实现一个带有内部存储器的 python 装饰器(由下面的counter表示)。

It seams the decorator variables are shared across instances of a dataclass while being different for instances of a common class .它接缝装饰器变量在dataclass实例之间共享,而对于公共class实例则不同。

Why is so?为什么会这样? And is there a cleaner/easier solution other than checking if f belongs to a class or not and if so, if the class is a dataclass ?除了检查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

Despide C and D being very similar, the code below shows that the instances of normal object have different counter each:尽管CD非常相似,但下面的代码显示普通object的实例每个都有不同的counter

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

While when using the dataclass instead, the counter is shared:而当使用dataclasscounter是共享的:

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

Decorator syntax is a shortcut for function application, so each use of @decorator is a separate call to decorator , each of which creates a new dict associated with the decorated function.装饰器语法是函数应用的快捷方式,因此每次使用@decorator都是对decorator的单独调用,每次调用都会创建一个与装饰函数关联的新dict

So it's one counter per decorated function, and there is one decorated function per class in your example.所以每个装饰函数有一个计数器,并且在您的示例中每个类有一个装饰函数。

But then there's another problem.但接下来还有另一个问题。 Your key depends on each class's __repr__ function, as *args includes the object itself.您的密钥取决于每个类的__repr__函数,因为*args包括对象本身。

For C , __repr__ is not defined, so object.__repr__ is used, producing a unique key for each instance.对于C__repr__未定义,因此使用object.__repr__ ,为每个实例生成唯一键。

For D , D.__repr__ returns a generic string 'D()' for every instance, so you aren't getting unique keys for instances of D .对于DD.__repr__为每个实例返回一个通用字符串'D()' ,因此您不会获得D实例的唯一键。

The solution is to be more explicit in constructing the key.解决方案是在构造密钥时更加明确。 Perhaps something like也许像

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

As you're decorating methods rather than functions, the value of *args in wrapper(*args, **kwargs) will be a one element tuple containing the implicit self .当您装饰方法而不是函数时, wrapper(*args, **kwargs)*args的值将是一个包含隐式self的单元素元组。

Your key value would then look like this ['foo', (<__main__.C object at 0x7fe8945403c8>,), {}] .然后,您的key将如下所示['foo', (<__main__.C object at 0x7fe8945403c8>,), {}]

As those instances of C and D get garbage collected, sometimes Python will reuse the same memory address and sometimes not, leading to different keys.CD那些实例被垃圾收集时,有时 Python 会重用相同的内存地址,有时不会,导致不同的键。

I'm not sure why Python would reuse dataclass addresses more than for regular classes.我不确定为什么 Python 会比常规类更多地重用数据类地址。

If you change wrapper to expect self , you should get consistent results.如果您将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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM