I've opened https://bugs.python.org/issue32953 to track this.
On 2/22/18 5:55 AM, Eric V. Smith 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.
...
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'
This is tricky to fix.
Here's the problem with a inheriting a non-frozen dataclass from a
frozen one. Consider class Y in this example:
@dataclass
class X:
x: int
@dataclass
class Y(X):
y: int
Y's __init__ looks like:
def __init__(self, x, y):
self.x = x
self.y = y
That is, all of the initializing for Y and its base classes which are
dataclasses is done in Y's __init__. There are a number of reasons for
this, including performance and not knowing how to call the base
classes' __init__ methods.
If Y is frozen, then the __init__ currently looks like:
def __init__(self, x, y):
object.__setattr__(self, 'x', x)
object.__setattr__(self, 'y', y)
If X is frozen but Y isn't, then it should look like:
def __init__(self, x, y):
object.__setattr__(self, 'x', x)
self.y = y
But I currently can't generate the code that way, because I don't know
if a base dataclass is frozen. That information is not saved on a dataclass.
I think the right thing to do is to record with each dataclass if it is
frozen or not, so that derived classes can generate the correct code.
Another option would be to always use object.__setattr__, but that will
hurt performance in the common case.
As long as I'm saving if a dataclass is frozen, I should save all of the
dataclass parameters on the class. Since it's per-class, it's not a lot
of overhead.
I should probably separate the two issues here: 1) deriving a
non-dataclass from a frozen dataclass and 2) deriving a non-frozen
dataclass from a frozen dataclass, but since they're somewhat related
I'm going to keep them together for the time being. #1 was Raymond's
initial report.
Eric
_______________________________________________
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