The class super
does not just recover the superclass. It instantiate an object which recovers methods in the context of a given method resolution order. Every class has a mro that you can access through the __mro__
attribute.
D.__mro__ # (D, B, C, A, object)
So when given a class and an instance, super
first recovers the mro from that instance. When you try to recover an attribute from the super
object, it returns it from the first class following the provided class that has such an attribute.
If you were to implement the behaviour of super
in Python, it would look something like this.
class super:
def __init__(self, cls, instance):
if not isinstance(cls, type):
raise TypeError('super() argument 1 must be type')
if isinstance(instance, cls):
self.mro = type(instance).__mro__
elif isinstance(instance, type) and issubclass(instance, cls):
self.mro = instance.__mro__
else:
raise TypeError('super(type, obj): obj must be an instance or subtype of type')
self.cls = cls
self.instance = instance
def __getattr__(self, attr):
cls_index = self.mro.index(self.cls)
for supercls in self.mro[cls_index + 1:]:
if hasattr(supercls, attr): break
# The actual implementation binds instances to methods before returning
return getattr(supercls, attr)
So back to your example, when you call super(B, self).go
, it recovers the __mro__
of self
, which is of type D
. It then picks go
from the first class following B
in the mro that has such an attribute.
So in this case since self.__mro__
is (D, B, C, A, object)
, the first class following B
that has the attribute go
is C
and not A
.
If you want details on how Python determines the mro, then I suggest abarnert's answer.