[Python-Dev] Should the dataclass frozen property apply to subclasses?
Nick Coghlan
ncoghlan at gmail.com
Thu Feb 22 21:43:09 EST 2018
On 22 February 2018 at 20:55, Eric V. Smith <eric at 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 | ncoghlan at gmail.com | Brisbane, Australia
More information about the Python-Dev
mailing list