Search code examples
pythonpython-3.xmultiple-inheritancesuperdiamond-problem

Why is an __init__ skipped when doing Base.__init__(self) in multiple inheritance instead of super().__init__()?


Why exactly is

A.__init__()
B.__init__()
D.__init__()

printed by the following code? In particular:

  1. Why is C.__init__() not printed?

  2. Why is C.__init__() printed if I put super().__init__() instead of A.__init__(self)?

#!/usr/bin/env python3

class A(object):
    def __init__(self):
        super(A, self).__init__()
        print("A.__init__()")
class B(A):
    def __init__(self):
        A.__init__(self)
        print("B.__init__()")
class C(A):
    def __init__(self):
        A.__init__(self)
        print("C.__init__()")
class D(B, C):
    def __init__(self):
        super(D, self).__init__()
        print("D.__init__()")

D()

Solution

  • tl;dr: because B.__init__ is what was supposed to call C.__init__ via super(B, self).__init__(), and you bypassed that call.


    Why is C.__init__() not printed?

    Because you didn't tell it to. Multiple inheritance involves cooperation and you've used explicit class references, denying that cooperation.

    Had you replaced all of the "super-like" calls with super().__init__() (since you've tagged it Python 3) you'd see output like:

    A.__init__()
    C.__init__()
    B.__init__()
    D.__init__()
    

    In fact, you'd see this output if you changed just B's "super-like" call to either:

    super(B, self).__init__()
    super().__init__()
    

    So why did A not call C in your case?

    It would be redundant to copy the well-outlined answers elsewhere on the site about the MRO.

    Why is C.__init__() printed if I put super().__init__() instead of A.__init__(self)?

    Because the no-argument super() goes left to right, so B is looked at first, and then inside B you're using an explicit class reference (A.__init__(self)). And in doing so you lose all (most*) context that D also had as a superclass C.

    super() is what helps you navigate the MRO, and would have gotten you to C.__init__() had you let it. But in B you're just calling a classmethod of A.

    *As you've noted C.__init__() is never called. However, C still shows up in D.__bases__:

    (<class '__main__.B'>, <class '__main__.C'>)
    

    in D.__mro__:

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

    and isinstance(D(), C) is True.

    In short, Python knows that C is a superclass of D, but you gave the C.__init__ and end-run with your B.__init__ implementation.