Consider the following code. I have a base and derived class, both dataclasses, and I want to call a method of the base class in the derived class via super()
:
import abc
import dataclasses
import typing
SLOTS = False
@dataclasses.dataclass(slots=SLOTS)
class Base:
@abc.abstractmethod
def f(self, x: int) -> int:
return x
@dataclasses.dataclass(slots=SLOTS)
class Derived(Base):
@typing.override
def f(self, x: int) -> int:
return super().f(x)
d = Derived()
d.f(2)
When setting SLOTS = False
, this runs fine. When setting SLOTS = True
, I get an error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[2], line 24
20 return super().f(x)
23 d = Derived()
---> 24 d.f(2)
Cell In[2], line 20, in Derived.f(self, x)
18 @typing.override
19 def f(self, x: int) -> int:
---> 20 return super().f(x)
TypeError: super(type, obj): obj (instance of Derived) is not an instance or subtype of type (Derived).
Why is that?
Note that it works if I use a regular slotted class instead of a dataclass:
import abc
import typing
class Base:
__slots__ = tuple()
@abc.abstractmethod
def f(self, x: int) -> int:
return x
class Derived(Base):
__slots__ = tuple()
@typing.override
def f(self, x: int) -> int:
return super().f(x)
d = Derived()
d.f(2)
Consider the following code. I have a base and derived class, both dataclasses, and I want to call a method of the base class in the derived class via super()
:
import abc
import dataclasses
import typing
SLOTS = False
@dataclasses.dataclass(slots=SLOTS)
class Base:
@abc.abstractmethod
def f(self, x: int) -> int:
return x
@dataclasses.dataclass(slots=SLOTS)
class Derived(Base):
@typing.override
def f(self, x: int) -> int:
return super().f(x)
d = Derived()
d.f(2)
When setting SLOTS = False
, this runs fine. When setting SLOTS = True
, I get an error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[2], line 24
20 return super().f(x)
23 d = Derived()
---> 24 d.f(2)
Cell In[2], line 20, in Derived.f(self, x)
18 @typing.override
19 def f(self, x: int) -> int:
---> 20 return super().f(x)
TypeError: super(type, obj): obj (instance of Derived) is not an instance or subtype of type (Derived).
Why is that?
Note that it works if I use a regular slotted class instead of a dataclass:
import abc
import typing
class Base:
__slots__ = tuple()
@abc.abstractmethod
def f(self, x: int) -> int:
return x
class Derived(Base):
__slots__ = tuple()
@typing.override
def f(self, x: int) -> int:
return super().f(x)
d = Derived()
d.f(2)
Share
Improve this question
asked Feb 17 at 18:09
rdong8rdong8
334 bronze badges
2
|
1 Answer
Reset to default 2WHen one uses the slots=True
option on dataclasses, it will create a new class using the namespace of the decorated class - while not passing it makes it modify the class "in place". (This is needed because using slots really change how the class is built - its "layout" as it is called)
The simplest thing to do is not to use __slots__
altogether - the gains slots gives one in modern Python are small since some of the optimizations that went on Python 3.11 (check it here: Are Python 3.11 objects as light as slots? )
Anyway, the workaround is quite simple - instead of the parameterless version of super(), which uses a mechanism introduced in Python 3.0, just use the explicit version of it, where you pass the class and instance as arguments.
The thing is that the parameterless version uses implicitly the class body
where the super()
call is actually written in: at compile time, the current class is frozen into the method, in a non-local like variable named __class__
. The new class created by dataclass with slots can't (or simply does not do) update this value - so parameterless super will try to call super on the original (pre dataclass decorator ) Derived
class and will fail.
This version of the code will work:
@dataclasses.dataclass(slots=SLOTS)
class Derived(Base):
@typing.override
def f(self, x: int) -> int:
return super(Derived, self).f(x)
super()
to be called without args, and how dataclasses add slots when used with a decorator. Using the old schoolsuper(Derived, self).f(x)
fixes the issue. – Dunes Commented Feb 17 at 18:19