Search code examples
pythonmultiple-inheritancesuper

Question on Python super multiple inheritance


I am new to Python, and I am really confused about the super function and multiple inheritance.

Here is the toy Code:

class A():
    def __init__(self):
        print('Call class A')        
        print('Leave class A')

class C(A):
    def __init__(self):
        print('Call class C')
        A.__init__(self)
        print('Leave class C')
class D(A):
    def __init__(self):
        print('Call class D')
        #A.__init__(self)
        super(D,self).__init__()
        print('Leave class D')
class B(A):
    def __init__(self):
        print('Call class B')
        super(B,self).__init__()
        print('Leave class B')

class E(C,B,D):
    def __init__(self):
        print('Call class E')
        B.__init__(self)
        #C.__init__(self)
        #D.__init__(self)
        print('Leave class E')

Then the output is:

Call class E
Call class B
Call class D
Call class A
Leave class A
Leave class D
Leave class B
Leave class E
(<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

where __init__ of Class D is called. However, if I change class E to:

class E(B,C,D):
    def __init__(self):
        print('Call class E')
        B.__init__(self)
        #C.__init__(self)
        #D.__init__(self)
        print('Leave class E')

where the order of B,C,D is changed, then the output is:

Call class E
Call class B
Call class C
Call class A
Leave class A
Leave class C
Leave class B
Leave class E
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

then the __init__ of class D will not be called, but class C's, although class does not use super function.

Does someone know: 1. In the first code, why __init__ of class D is also called? 2. In the second code, why __init__ of class C is called and __init__ of class D is not?


Solution

  • Failing to use super consistently means you aren't guaranteeing that the MRO will be walked properly.

    In your first example, the explicit call to B.__init__ in E.__init__ ensures that C.__init__ never gets called, because B comes after C in the MRO.

    In the second example, B just happens to be the next class after E, so nothing broke (yet). But then in C.__init__, you again use A.__init__(self) instead of super(C, self).__init__(), which means you jumped straight from C to A, bypassing D which should have come next.

    When designing a class hierarchy that uses super (not just a single class), you have to ensure that every class uses super to ensure that the MRO selected by the runtime type of self is followed, rather than hard-coding calls to parent class methods at compile-time.

    The correct definition would be

    class A:
        def __init__(self):
            print('Call class A')
            super().__init__()       
            print('Leave class A')
    
    class C(A):
        def __init__(self):
            print('Call class C')
            super().__init__()
            print('Leave class C')
    class D(A):
        def __init__(self):
            print('Call class D')
            super().__init__()
            print('Leave class D')
    class B(A):
        def __init__(self):
            print('Call class B')
            super().__init__()
            print('Leave class B')
    
    class E(C,B,D):
        def __init__(self):
            print('Call class E')
            super().__init__(self)
            print('Leave class E')
    

    In Python 3, some compiler magic provides the "default" arguments when super is called without any. For example, in E.__init__, super().__init___() is equivalent to super(C, self).__init__().

    Because the value of self (and thus its type) is constant throughout the chain of calls, each use of super() refers to the next class in the MRO for type(self). In the above example, that would thus produce the output

    Call class E
    Call class C
    Call class B
    Call class D
    Call class A
    Leave class A
    Leave class D
    Leave class B
    Leave class C
    Leave class E
    

    If you change the order of base classes in the definition of E, the MRO (and thus the output) will update automatically without having to change any further code.