4

I wonder how is class C being executed in this code flow When none of the flow calls it.

class A:
        def fun1(self):
            print("This is class A")


class B(A):
    def fun2(self):
        super().fun1()
        print("This is class B")


class C(A):
    def fun1(self):
        super().fun1()
        print("This is class C")


class D(B, C):
    def fun1(self):
        self.fun2()
        print("This is class D")


obj = D()
obj.fun1()

"""
# Result of this code is 
This is class A
This is class C
This is class B
This is class D
"""
Ajay A
  • 1,030
  • 1
  • 7
  • 19

1 Answers1

4

The method resolution order used is a C3 linearization algorithm, and super itself uses this MRO. The resolution for a type is obtained with the mro() method, and cached in the __mro__ attribute:

>>> D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)
>>> D.mro()
[__main__.D, __main__.B, __main__.C, __main__.A, object]

Since you call print after calling super, you'll see the reverse of this, i.e. A -> C -> B -> D.

I wonder how is class C being executed in this code flow when none of the flow calls it.

D.fun2 doesn't exist, so obj.fun2() gets resolved (via the MRO) on B instead:

>>> obj = D()
>>> obj.fun2
<bound method B.fun2 of <__main__.D object at 0x7fffe89a3cd0>>

Then in B.fun2, the C.fun1 gets called here:

class B(A):
    def fun2(self):
        super().fun1()  # <-- this resolves to C.fun1, not A.fun1!
        print("This is class B")

C.fun1 is called since it's D's MRO which is active here, not B's, because the type(self) is D. Note that super() does not always resolve on the parent class, it can be resolved on a sibling class like in your example. Python's implementation was more or less lifted from another programming language called Dylan, where super was named next-method, a less confusing name in my opinion.

wim
  • 338,267
  • 99
  • 616
  • 750