8

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?

enkryptor
  • 1,574
  • 1
  • 17
  • 27
  • 1
    *"`slots`: [..] `__slots__` attribute will be generated __and new class will be returned instead of the original one__"* — https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass. So it does mess with the class when you use `slots`. – deceze Aug 04 '23 at 13:33
  • @deceze so when I specify `B` explicitly in `super(B, self)`, is this the new class or the original one? – enkryptor Aug 04 '23 at 13:47
  • It's the new class. `B` is not looked up until `get_data` is *called*, by which time the name has been rebound to the new class. When `super` has no arguments, the class used as its "default" argument is determined while the class is being defined, *before* that class gets passed to `dataclass` to be replaced. – chepner Aug 04 '23 at 13:49
  • (It's not entirely clear to me how to *prove* this via inspection of `B`, though. ) – chepner Aug 04 '23 at 13:51

1 Answers1

2

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.

deceze
  • 510,633
  • 85
  • 743
  • 889