MrSonic
MrSonic

Reputation: 363

python 'super' in multiple inheritance - unexpected code output

I am relatively new to python OOP. Although I have some experience with JAVA OOP and I know the meaning of the method 'super', I struggle to understand how does it works in python when I have multiple inheritances (which doesn't exist in JAVA)

After digging to find some answers, I read that according to the inheritances graph, for every class python made a Method Resolution Order (MRO) to determine in what order to look for an instance method.

I also read that the MRO is determined by "old style" or "new style", depending on my python version. I have python 3.7 so I use the "new style" method.

If I understand correctly, every time I override a method and call 'super', python goes to the method in the class that appears after the current class in the MRO.

Indeed, When I run the following code:

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

if __name__ == "__main__":
    d = D()
    d.go()
    print(D.__mro__)

I got the output:

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

So far so good, but when I tried to run the following code:

class A(object):
    def foo(self):
        print('this is A')

class B(object):
    def foo(self):
        print('this is B')

class AB(A,B):
    def foo(self):
        super(AB,self).foo()
        print('this is AB')

if __name__ == '__main__':
    x = AB()
    x.foo()
    print(AB.__mro__)

I got the output:

this is A
this is AB
(<class '__main__.AB'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

Rather than the output as I was expected to be:

this is B
this is A
this is AB
(<class '__main__.AB'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

So apparently I don't understand what is going on...

Any explanation for this situation, and also about how exactly python determines the MRO (according to the "new style") will be greatly appreciated!

Upvotes: 2

Views: 186

Answers (1)

chepner
chepner

Reputation: 532053

The problem is that your classes are not cooperatively defined. Both A and B think that it has introduce foo, and therefore there is no need to call super().foo, because each thinks that it will be the last class in any potential MRO to define foo. AB proves that is not the case.

When you call AB.foo, the first thing it does is call A.foo. However, A.foo doesn't use super, so the chain ends, and B.foo never gets called.

In a properly designed hierarchy, exactly one class "introduces" a method, and is responsible for terminating the chain by not calling super. Any other class that wants to be part of the hierarchy is responsible for calling super.

In your A/B/AB case, you have a few options:

  1. Have both A and B inherit from FooBase, and have each of them call super().foo() from its implementation of foo. FooBase.foo itself does not call super.

  2. Instead of AB inheriting directly from A and B, have it inherit from wrappers around one or both of them, where the wrapper correctly implements cooperative inheritance. (See Python's super() considered super!. for more details.)

    As an example, here we wrap A and let B act as the base for foo:

    class A:
        def foo(self):
            print('this is A')
    
    
    class AWrapper:
        def __init__(self, **kwargs):
            super().__init__()
            self.a = A()
    
        def foo(self):
            super().foo()
            self.a.foo()
    
    
    class B(object):
        def foo(self):
            print('this is B')
    
    # Important: AWrapper must come first
    class AB(AWrapper, B):
        def foo(self):
            super().foo()
            print('this is AB')
    
    AB().foo()
    

Note this gets more complicated if __init__ itself needs to be defined in A and B; see the linked article for more advice on making __init__ work properly in a cooperative inheritance setting.

Upvotes: 1

Related Questions