You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Frozen dataclasses with slots raise TypeError #90055
When making a dataclass with slots=True and frozen=True, assigning to an invalid attribute raises a TypeError rather than a FrozenInstanceError:
>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True)
... classVector:
... x: float
... y: float
... z: float
...
>>> v = Vector(1, 2, 3)
>>> v.a =4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 5, in __setattr__
TypeError: super(type, obj): obj must be an instance or subtype of type
This looks to be due to the fact that slots=True leads to the creation of an entirely new class (see line 1102), meaning that in the super(cls, self) calls in lines 611 and 618 (in the _frozen_get_del_attr function, responsible for generating __setattr__ and __delattr__ methods), self is no longer an instance of cls.
I believe this can be fixed by tweaking _frozen_get_del_attr so that cls in the generated __setattr__ and __delattr__ methods is dynamically computed (cls = type(self)), rather than read from a closure, as is currently the case.
You get the same error if you subclass a frozen dataclass, then try to set an attribute that is not one of the superclass's __slots__:
>>> @dataclass(slots=True, frozen=True)
... class Point:
... x: int
... y: int
...
...
>>> class Subclass(Point): pass
...
>>> s = Subclass(1, 2)
>>> s.z = 5
Traceback (most recent call last):
File "<pyshell#15>", line 1, in <module>
s.z = 5
File "<string>", line 7, in __setattr__
TypeError: super(type, obj): obj must be an instance or subtype of type
I believe this can be fixed by tweaking _frozen_get_del_attr so that cls in the generated __setattr__ and __delattr__ methods is dynamically computed (cls = type(self)), rather than read from a closure, as is currently the case.
It looks like this is not the approach you took, but to be clear, using type(self) for the cls argument to super() does not work. It seems to work when you just have the one class, then becomes an infinite recursion error when you subclass the class (an empty stub subclass is all you need) and try to call the method in question on an instance of the subclass. It needs to be read from the closure because the cls in question must be a definition-time association (the method itself is inherently linked to the class it was defined in); at runtime, there's no other source that will tell you where you were defined (it's why Python hides a __class__ closure scope variable in every method that references __class__ or super, just in case).
I believe this can be fixed by tweaking _frozen_get_del_attr so that cls in the generated __setattr__ and __delattr__ methods is dynamically computed (cls = type(self)), rather than read from a closure, as is currently the case.
It looks like this is not the approach you took, but to be clear, using type(self) for the cls argument to super() does not work. It seems to work when you just have the one class, then becomes an infinite recursion error when you subclass the class (an empty stub subclass is all you need) and try to call the method in question on an instance of the subclass.
Yes, this is why this is not the approach I took :) thanks for updating the issue though! I forgot that I'd left that comment
Okay, I'm finally getting a chance to look at this. A few things:
I don't think we can change this in a minor release (3.10 or 3.11, at this point).
Is this even worth fixing? If the code had always raised AttributeError, I would be happy with it. But is the cost of disruption worth it? I realize that very few people probably actually catching this exception, and the value here is at the REPL to give a better error message. But still, there's probably more than zero people this would affect.
I'm going to close this. If someone really feels strongly that the exception needs to change, please open a discussion on discuss.python.org. We can re-open this issue if a discussion there produces some consensus.
raises dataclasses.FrozenInstanceError: cannot assign to field 'z' instead of TypeError: super(type, obj): obj (instance of Point) is not an instance or subtype of type (Point).
Activity
treyhunner commentedon Nov 25, 2021
When making a dataclass with slots=True and frozen=True, assigning to an invalid attribute raises a TypeError rather than a FrozenInstanceError:
AlexWaygood commentedon Nov 25, 2021
This looks to be due to the fact that
slots=Trueleads to the creation of an entirely new class (see line 1102), meaning that in thesuper(cls, self)calls in lines 611 and 618 (in the_frozen_get_del_attrfunction, responsible for generating__setattr__and__delattr__methods),selfis no longer an instance ofcls.I believe this can be fixed by tweaking
_frozen_get_del_attrso thatclsin the generated__setattr__and__delattr__methods is dynamically computed (cls = type(self)), rather than read from a closure, as is currently the case.ericvsmith commentedon Nov 25, 2021
I think the error should be AttributeError, which is what you'd get if the class weren't frozen.
AlexWaygood commentedon Dec 2, 2021
You get the same error if you subclass a frozen dataclass, then try to set an attribute that is not one of the superclass's __slots__:
MojoVampire commentedon May 3, 2022
It looks like this is not the approach you took, but to be clear, using
type(self)for theclsargument tosuper()does not work. It seems to work when you just have the one class, then becomes an infinite recursion error when you subclass the class (an empty stub subclass is all you need) and try to call the method in question on an instance of the subclass. It needs to be read from the closure because theclsin question must be a definition-time association (the method itself is inherently linked to the class it was defined in); at runtime, there's no other source that will tell you where you were defined (it's why Python hides a__class__closure scope variable in every method that references__class__orsuper, just in case).AlexWaygood commentedon May 3, 2022
Yes, this is why this is not the approach I took :) thanks for updating the issue though! I forgot that I'd left that comment
ericvsmith commentedon Oct 3, 2022
Okay, I'm finally getting a chance to look at this. A few things:
ericvsmith commentedon Oct 4, 2022
I'm going to close this. If someone really feels strongly that the exception needs to change, please open a discussion on discuss.python.org. We can re-open this issue if a discussion there produces some consensus.
sndrtj commentedon Aug 22, 2026
As a workaround, setting
__slots__manually gives the normal, expected exception.E.g.
raises
dataclasses.FrozenInstanceError: cannot assign to field 'z'instead ofTypeError: super(type, obj): obj (instance of Point) is not an instance or subtype of type (Point).