python 冻结数据类 object.__setattr__ 不可变

python frozen dataclass immutable with object.__setattr__

在我遇到数据类之前,我将命名元组用于不可变数据结构,我在用例中更喜欢它(与问题无关)。

现在我知道它们不是一成不变的!至少严格来说不是。

setattr(frozen_dc_obj, "prop", "value") 引发异常。好的
但为什么 object.__setattr__(frozen_dc_obj,..) 有效?

namedtuple相比,它引发了异常!

from collections import namedtuple
from dataclasses import dataclass


NTTest = namedtuple("NTTest", "id")

nttest = NTTest(1)
setattr(nttest, "id", 2)  # Exception
object.__setattr__(nttest, "id", 2)  # Exception


@dataclass(frozen=True)
class DCTest:
    id: int

dctest = DCTest(1)
setattr(dctest, "id", 2)  # Exception
object.__setattr__(dctest, "id", 2)  # WORKS

namedtuple defines __slots__ = () 因此您不能设置任何属性(它没有 __dict__)。

Frozen dataclasses on the other hand perform a manual check in their __setattr__ 方法并在它是冻结实例时引发异常。

比较以下:

>>> class Foo:
...     __slots__ = ()
... 
>>> f = Foo()
>>> f.__dict__  # doesn't exist, so object.__setattr__ won't work
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__dict__'
>>> @dataclass(frozen=True)
... class Bar:
...     pass
... 
>>> b = Bar()
>>> b.__dict__  # this exists, so object.__setattr__ works
{}

python Discord 频道上的其他人提供了另一个很好的见解:

冻结的数据class可能通过它自己的 setattr 限制访问,您可以通过将其传递给对象来绕过它。

命名元组派生自实际的元组而不是 python 中的实现,因此它们可以是“真正的”不可变的,但如果数据class(同样可以以更混乱的方式对元组进行)。 我建议使用 NamedTuple 来代替 namedtuple,因为它提供了适当的 class 的可读性,但如果您需要数据 class,那么冻结的数据应该没问题。 NamedTuple 并不是真正的类型提示,但在 class 定义中与它们一起使用,就像 dataclasses 所做的那样,并为它提供了比 type-like 构造函数更简洁的定义。

我认为没有一种方法可以限制纯 python 中的所有访问,因为您可以调用对象的有效方法。

在元组上赋值将涉及在 C 级别与 (c)python 交互,但在某种意义上是相同的,因为它不是它的预期接口。

Python 总的来说,这样的安全性并不大,如果不应该做某事,那么如果他们做了,那是用户的问题。