Veljko
Veljko

Reputation: 1808

Python MRO - usage of super in multiple inheritance

I have this code where super and multiple inheritance are used. Result of the class is:

go A go!
go C go!
go B go!
go D go!

While I would expect:

go A go!
go B go!
go D go!

From my understanding: D because of the MRO invokes class B because go is implemented in B. Class B invokes super of its parent A. A is executed, that is OK. Then I would expect to B continues execution so it means B is executed and finally D is executed. But of course this is not correct. Why does it enter C since definition of the go method is found in B and then it should not search anymore in the C. That is how MRO works. It founds in first class and it should not search anymore. Totally confused :(

 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!")

a = A()
b = B()
c = C()
d = D()

d.go()

Upvotes: 5

Views: 1268

Answers (3)

gelonida
gelonida

Reputation: 5630

I personally don't use inheritance that often and avoid multiple inheritance, except in very rare cases.

It is trickier as one expects.

I think you will understand better how things work if you change your code slightly by first printing and then calling super:

The only real difference is, that I print first and call then super. Apart from that I use the lazier python3 only syntax (no need to explicitly inherit from object, no need to pass params to super() if called from within a method, they will be automatically filled in correctly), but this doesn't change anything.

class A:
        def go(self):
            print("go A go!")

class B(A):
    def go(self):
        print("go B go!")
        super().go()

class C(A):
    def go(self):
        print("go C go!")
        super().go()

class D(B,C):
    def go(self):
        print("go D go!")
        super().go()

b = B()
print(b.__class__)
print(b.__class__.__mro__)
d = D()
print(d.__class__)
print(d.__class__.__mro__)
d.go()

So what you see first is the classes and MROs of b and d, which should not come as a surprise.

if D has a method it will be called, if it doesn't look for the method in B, if not in C, if not in A

Thus D.go() will be called first.

super(D, self).go() called from D.go() will look for the next entry in the MRO of self.__class__.__mro__ . remember we were at D, so it looks at B, where it will find a go method and call it.

Now where things behave differently as you expect is, that the go() method in B does not as you expect look at the MRO of B , it continues to look at the next entry in the MRO of self.__class__, which is C and not A you expected, because self.__class__ is of course still D

Hope this helps understanding.

The output of above script will be:

<class '__main__.B'>
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
<class '__main__.D'>
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
go D go!
go B go!
go C go!
go A go!

Upvotes: 1

Holloway
Holloway

Reputation: 7367

There is an excellent video from a PyCon by Raymond Hettinger (core python developer) here.

The main take away is that super doesn't call your parents, it calls your descendants' ancestors. So whenever you call super, it goes back to the instance you're actually calling the method on and looks for the next type in the MRO (which is a guaranteed order even with multiple inheritance and whyclass D(B,C) isn't the same as class D(C, B)).

In your case the MRO for D is (D,B,C,A). When D.go calls super, it calls B.go. When that calls super it is still using D's MRO so it moves to the next type, in your case C which in turn calls A. The end result is that your print statements are executed in D's MRO reversed.

There is also a blog by RH here which also covers the same topics if you can't watch youtube.

Upvotes: 3

chepner
chepner

Reputation: 531165

When you call d.go(), each call to super uses the MRO of the invoking instance (in this case, d with an MRO of D, B, C, A, object. super does not necessarily refer to the parent of the class where it statically appears.

This is most apparent in the defintion of B.go. Even though the definition of B knows nothing about C, its self argument is a reference to an object of type D, and the next class in D's MRO after B is C, not A.

Upvotes: 0

Related Questions