I'm using the attrs python package, in combination with inheritance and slots. I want to call the parent class's method from within the derived method. The problem is demonstrated below:
import attr
@attr.s(slots=True)
class Base():
def meth(self):
print("hello")
@attr.s(slots=True)
class Derived(Base):
def meth(self):
super().meth()
print("world")
d = Derived()
d.meth()
I get:
TypeError: super(type, obj): obj must be an instance or subtype of type
The problem seems to be triggered by a combination of attrs (undecorated classes with explicit __slots__=() work), slots (regular @attr.s-decorated classes work) and the plain super() call (super(Derived, self) works).
I would like to understand how super() behaves differently from the explicit super(Derived, self) version, since the documentation says they "do the same thing"
super()normally relies on the compiler to provide a__class__closure cell, that is bound to the class object a method is derived on. The closure is created the moment you use the namesuper()in a method (or if you used__class__):That closure lets
super()work without arguments (theselfargument is taken from the local namespace).However,
attrgenerates a new class object when you specify you wanted to use__slots__; you can't add slots to a class after the fact, so a new class object is created that replaces the one you decorated.The closure attached to
methis the original pre-decoration class and not the same class object as the new generated class:This breaks the expectations
super()has, making it impossible to use the 0 argument variant. Thesuper(Derived, self)variant explicitly looks up the nameDerivedas a global when called, finding the new generated class, so works.I go into some more detail on how
super()without arguments works, and why, in Why is Python 3.x's super() magic?This was reported as issue #102 in the tracker, and fixed by altering the closure with some
ctypeshackery. This fix will be part of the upcoming 17.3 release.