当 class 变量被分配为列表时,python 中的数据class 不会引发错误(但会出现键入提示)

Dataclass in python does not raise error when the class variable is assigned as a list (but does with typing hints)

我只是想让自己熟悉 python 中的数据class。我从网上的一些阅读中学到的一件事是,我们可以将带有可变 class 变量(这是一件坏事)的常规 class 定义转换为 dataclass ,这样可以防止它。例如:

常规 class:

class A:
    a = []
    
    def __init__(self):
        self.b = 1

这可能会导致不同实例共享相同 class 变量 a 并在不知不觉中修改 a 的潜在问题。

和数据class:

@dataclass
class A:
    a: list = []

    def __init__(self):
        self.b = 1

这不允许我通过引发错误来写这个 class:

ValueError: mutable default <class 'list'> for field a is not allowed: use default_factory

但是,如果我简单地去掉类型注释:

@dataclass
class A:
    a = []

    def __init__(self):
        self.b = 1

完全没有投诉,a仍然在不同实例之间共享。

这是预期的吗?

为什么简单类型注释会改变 class 变量的行为?

(我正在使用 python 3.7.6

当你申报时

@dataclass
class A:
    a = []

    def __init__(self):
        self.b = 1

a 不是数据class 字段。参考:https://github.com/ericvsmith/dataclasses/issues/2#issuecomment-302987864

您可以在声明 class 后查看 __dataclass_fields____annotations__ 字段。

In [55]: @dataclass
    ...: class A:
    ...:     a: list = field(default_factory=list)
    ...:
    ...:     def __init__(self):
    ...:         self.b = 1
    ...:

In [56]: A.__dict__
Out[56]:
mappingproxy({'__module__': '__main__',
              '__annotations__': {'a': list},
              '__init__': <function __main__.A.__init__(self)>,
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              '__doc__': 'A()',
              '__dataclass_params__': _DataclassParams(init=True,repr=True,eq=True,order=False,unsafe_hash=False,frozen=False),
              '__dataclass_fields__': {'a': Field(name='a',type=<class 'list'>,default=<dataclasses._MISSING_TYPE object at 0x7f8a27ada250>,default_factory=<class 'list'>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)},
              '__repr__': <function dataclasses.__repr__(self)>,
              '__eq__': <function dataclasses.__eq__(self, other)>,
              '__hash__': None})

In [57]: @dataclass
    ...: class A:
    ...:     a = []
    ...:
    ...:     def __init__(self):
    ...:         self.b = 1
    ...:

In [58]: A.__dict__
Out[58]:
mappingproxy({'__module__': '__main__',
              'a': [],
              '__init__': <function __main__.A.__init__(self)>,
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              '__doc__': 'A()',
              '__dataclass_params__': _DataclassParams(init=True,repr=True,eq=True,order=False,unsafe_hash=False,frozen=False),
              '__dataclass_fields__': {},
              '__repr__': <function dataclasses.__repr__(self)>,
              '__eq__': <function dataclasses.__eq__(self, other)>,
              '__hash__': None})

来自 PEP 557:

dataclass 装饰器检查 class 以查找字段。字段定义为 __annotations__ 中标识的任何变量。即,具有类型注释的变量。参考:

检查仅发生在数据class 字段上而不发生在 class 变量上,这是导致错误的字段检查

  if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)):

为什么不允许可变类型:https://docs.python.org/3/library/dataclasses.html#mutable-default-values