I used namedtuples for immutable data structures until I came across dataclasses, which I prefer in my use-cases (not relevant to the question).
Now I learned that they are not immutable! At least not strictly speaking.
setattr(frozen_dc_obj, "prop", "value")
raises an exception. ok.
But why does object.__setattr__(frozen_dc_obj,..)
work?
Compared to namedtuple
, where it raises an exception!
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__ = ()
and hence you can't set any attribute (it doesn't have a __dict__
).
Frozen dataclasses on the other hand perform a manual check in their __setattr__
method and raise an exception if it's a frozen instance.
Compare the following:
>>> 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
{}