Repository navigation
Frozen dataclasses with slots raise TypeError #90055
Description
Activity
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) ... class Vector: ... 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
- added3.10 (EOL)end of lifeend of lifetype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on 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.- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Nov 25, 2021 I think the error should be AttributeError, which is what you'd get if the class weren't frozen.
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 typeI 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.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).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.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.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
Reacted by Josh {*()} RosenbergOkay, 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.
Reacted by Trey HunnerI'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.
As a workaround, setting
__slots__manually gives the normal, expected exception.E.g.
@dataclasses.dataclass(frozen=True) class Point: __slots__ = ("x", "y") x: int y: int point = Point(x=1, y=1) point.z = 1
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).
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields: