11

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()
user541686
  • 205,094
  • 128
  • 528
  • 886

1 Answers1

6

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.

user541686
  • 205,094
  • 128
  • 528
  • 886
jedwards
  • 29,432
  • 3
  • 65
  • 92
  • I can't make sense of this after reading your answer. How and why exactly should the lack of `super` in the **first superclass** affect whether there is a call to the function in the **second superclass**, when the other classes are kept the same? You're just telling me "it involves cooperation" and that I could fix it by calling `super`.. that's obvious in the question. The question is what does that actually mean internally, and how do I make sense of it? Does it mean `super` maintains state between invocations, for example? What state does it hold, and why? How does that get screwed up? – user541686 Oct 07 '18 at 06:32
  • 1
    `super` doesn't maintain state, but can be used to find the next class on the MRO easily. You don't *need* `super()`, you could have done "what `super()` would have done" by calling `C.__init__` inside of `B`. If you think that would look weird, that unrelated classes should reference each other that way I agree, and that's why `super()` exists. – jedwards Oct 07 '18 at 06:37
  • `super()` takes two things, (a) the current class, and (b) the `self` instance -- it uses the MRO of (b) and the position of (a) on it to find the next class in the MRO. In Python3 these things don't need to be explicitly supplied. – jedwards Oct 07 '18 at 06:40
  • Re: *How and why exactly should the lack of super in the first superclass affect whether there is a call to the function in the second superclass?* Because ***the thing*** that gets you from B to C (in the case of your `D` instance, at least) ***is super()***. – jedwards Oct 07 '18 at 06:41
  • 1
    Holy cow, `B.__init__` is calling `C.__init__`?!? Wow. Thanks, that makes sense now!! – user541686 Oct 07 '18 at 06:44
  • @Mehrdad correct :) (provided `B.__init__` uses `super()` of course) – jedwards Oct 07 '18 at 06:47
  • Yeah, wow. It never hit me that could be happening, though in hindsight I could've figured it out with some prints if I had just thought of the possibility! – user541686 Oct 07 '18 at 06:52