Search code examples
pythonpython-dataclassesslots

TypeError when using super() in a dataclass with slots=True


I have a dataclass with (kind of) a getter method.

This code works as expected:

from dataclasses import dataclass

@dataclass()
class A:
    def get_data(self):
        # get some values from object's fields
        # do some calculations
        return "a calculated value"

@dataclass()
class B(A):
    def get_data(self):
        data = super().get_data()
        return data + " (modified)"


b = B()

print(b.get_data())  # a calculated value (modified)

However, if I add slots=True, I get a TypeError:

from dataclasses import dataclass

@dataclass(slots=True)
class A:
    def get_data(self):
        return "a calculated value"

@dataclass(slots=True)
class B(A):
    def get_data(self):
        data = super().get_data()
        return data + " (modified)"


b = B()

print(b.get_data())  # TypeError: super(type, obj): obj must be an instance or subtype of type

The error vanishes if I use the old-style super(), contrary to pep-3135:

from dataclasses import dataclass

@dataclass(slots=True)
class A:
    def get_data(self):
        return "a calculated value"

@dataclass(slots=True)
class B(A):
    def get_data(self):
        data = super(B, self).get_data()
        return data + " (modified)"


b = B()

print(b.get_data())  # a calculated value (modified)

Why does this happen and how to fix it the right way?


Solution

  • slots: If true (the default is False), __slots__ attribute will be generated and new class will be returned instead of the original one. If __slots__ is already defined in the class, then TypeError is raised.

    https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass

    Taking this as an example:

    @dataclass(slots=True)
    class Foo:
        pass
    

    This means this works something like this:

    class Foo:
        pass
    
    Foo = dataclass(slots=True)(Foo)
    

    You define a class Foo, and then it gets replaced with a different, altered class.

    Now, your method:

    def get_data(self):
        data = super().get_data()
        ...
    

    This super() was written in the original Foo class and will assume it's supposed to look up the parent of this original Foo class; but the instance it currently has is not actually an instance of that class, it's an instance of the other, altered class.

    When you do this instead:

    data = super(Foo, self).get_data()
    

    This looks up what currently refers to the name Foo, which again matches what self also refers to at this moment.