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

Reply via email to