There is a famous Python example
class A(object):
def go(self):
print("go A go!")
class B(A):
def go(self):
super(B, self).go()
print("go B go!")
class C(A):
def go(self):
super(C, self).go()
print("go C go!")
class D(B,C):
def go(self):
super(D, self).go()
print("go D go!")
d = D()
d.go()
#go A go!
#go C go!
#go B go!
#go D go!
I have several questions. The first one is B calls A and C calls A so I expect A to appear twice. The second question is about the order.
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.So when given a class and an instance,
super
first recovers the mro from that instance. When you try to recover an attribute from thesuper
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.So back to your example, when you call
super(B, self).go
, it recovers the__mro__
ofself
, which is of typeD
. It then picksgo
from the first class followingB
in the mro that has such an attribute.So in this case since
self.__mro__
is(D, B, C, A, object)
, the first class followingB
that has the attributego
isC
and notA
.If you want details on how Python determines the mro, then I suggest abarnert's answer.
Since Python 2.3, method resolution has used an algorithm called C3 Linearization (borrowed from Dylan). Wikipedia has a nice article on it.
As the name implies, the idea is to force the method resolution graph to be a straight line, even if the inheritance graph isn't. Which means
A
is not going to appear twice, by design.Why? Well, for one thing, it completely avoids the "diamond problem" that plagues multiple inheritance in many other languages. (Or maybe it's more accurate to say that many other languages either ban MI or restrict it to pure "interfaces" because they didn't have a solution to the problem as it exists in C++.)
The original Python explanation—including the motivation behind it—is available in The Python 2.3 Method Resolution Order. This is a bit technical, but worth reading if you're interested.
You might also want to read the original Dylan paper, which goes into more detail about what's wrong with non-linear MRO graphs, and the challenges of coming up with a linearization that's monotonic (i.e., it goes in the order you expect, or at least the order you expect once you get over the fact that it's linear), and so on.
And if you want a deeper understanding of how
type()
works under the covers, or just want to see what's changed between 2.3 and 3.7 (e.g., the way__mro__
gets created and updated—although magic 3.xsuper
is elsewhere), there's really no better place than the CPython source.