On 22 February 2018 at 20:55, Eric V. Smith <e...@trueblade.com> wrote: > On 2/22/2018 1:56 AM, Raymond Hettinger wrote: >> >> When working on the docs for dataclasses, something unexpected came up. >> If a dataclass is specified to be frozen, that characteristic is inherited >> by subclasses which prevents them from assigning additional attributes: >> >> >>> @dataclass(frozen=True) >> class D: >> x: int = 10 >> >> >>> class S(D): >> pass >> >> >>> s = S() >> >>> s.cached = True >> Traceback (most recent call last): >> File "<pyshell#49>", line 1, in <module> >> s.cached = True >> File >> "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/dataclasses.py", >> line 448, in _frozen_setattr >> raise FrozenInstanceError(f'cannot assign to field {name!r}') >> dataclasses.FrozenInstanceError: cannot assign to field 'cached' > > > This is because "frozen-ness" is implemented by adding __setattr__ and > __delattr__ methods in D, which get inherited by S. > >> Other immutable classes in Python don't behave the same way: >> >> >> >>> class T(tuple): >> pass >> >> >>> t = T([10, 20, 30]) >> >>> t.cached = True >> >> >>> class F(frozenset): >> pass >> >> >>> f = F([10, 20, 30]) >> >>> f.cached = True >> >> >>> class B(bytes): >> pass >> >> >>> b = B() >> >>> b.cached = True > > > The only way I can think of emulating this is checking in __setattr__ to see > if the field name is a field of the frozen class, and only raising an error > in that case.
If you were going to do that then it would likely make more sense to convert the frozen fields to data descriptors, so __setattr__ only gets called for attempts to add new attributes. Then for the `frozen=False` case, the decorator could force __setattr__ and __delattr__ back to the default implementations from object, rather than relying on the behaviour inherited from base classes. > A related issue is that dataclasses derived from frozen dataclasses are > automatically "promoted" to being frozen. > >>>> @dataclass(frozen=True) > ... class A: > ... i: int > ... >>>> @dataclass > ... class B(A): > ... j: int > ... >>>> b = B(1, 2) >>>> b.j = 3 > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > File "C:\home\eric\local\python\cpython\lib\dataclasses.py", line 452, in > _frozen_setattr > raise FrozenInstanceError(f'cannot assign to field {name!r}') > dataclasses.FrozenInstanceError: cannot assign to field 'j' > > Maybe it should be an error to declare B as non-frozen? It would be nice to avoid that, as a mutable subclass of a frozen base class could be a nice way to model hashable-but-mutable types: >>> @dataclass(frozen=True) # This is the immutable/hashable bit ... class A: ... i: int ... >>> @dataclass # This adds the mutable-but-comparable parts ... class B(A): ... j: int ... __hash__ = A.__hash__ However, disallowing this case for now *would* be a reasonable way to postpone making a decision until 3.8 - in the meantime, folks would still be able to experiment by overriding __setattr__ and __delattr__ after the dataclass decorator sets them. Cheers, Nick. -- Nick Coghlan | ncogh...@gmail.com | Brisbane, Australia _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com