12

I have read Expert Python Programming which has an example for multi-inheritance. The book author has explained but I did not understand it, so I would like to have another view.

The example shows that object B is created two times!

Could you please give me an intuitive explanation.

In [1]: class A(object):
    ...:     def __init__(self):
    ...:         print "A"
    ...:         super(A, self).__init__()

In [2]: class B(object):
    ...:     def __init__(self):
    ...:         print "B"
    ...:         super(B, self).__init__()

In [3]: class C(A,B):
    ...:     def __init__(self):
    ...:         print "C"
    ...:         A.__init__(self)
    ...:         B.__init__(self)

In [4]: print "MRO:", [x.__name__ for x in C.__mro__]
MRO: ['C', 'A', 'B', 'object']

In [5]: C()
C
A
B
B
Out[5]: <__main__.C at 0x3efceb8>

The book author said:

This happens due to the A.__init__(self) call, which is made with the C instance, thus making super(A, self).__init__() call B's constructor

The point from which I didn't get its idea is how A.__init__(self) call will make super(A, self).__init__() call B's constructor

Mike Müller
  • 82,630
  • 20
  • 166
  • 161
Bryan
  • 1,477
  • 1
  • 21
  • 38
  • Why are you not using `super` in C's init method? – Tom Dalton Feb 25 '16 at 12:14
  • I just want to understand how an object is created in Python. – Bryan Feb 25 '16 at 12:16
  • 3
    If you think of the `super` call as meaning 'call the next method in the MRO', rather than 'call my parent class's method', then this behaviour should make more sense. In this case, the reason you see B printed twice is because super already arranges for B's init to be called (by A's init), and so when you explicitly call B.init from C, you get a second call. – Tom Dalton Feb 25 '16 at 12:18
  • 1
    See Raymond Hettinger's talk on super, might help make more sense: https://www.youtube.com/watch?v=EiOglTERPEo – Tom Dalton Feb 25 '16 at 12:19
  • 2
    Regarding your question title, the object is not "created twice". The `__init__` function is called twice but the object was already created before that. – interjay Feb 25 '16 at 12:21
  • 1
    Clarifying @interjay's comment, the __init__() method is an *initializer*, not a *constructor*. The C instance is created by __new__(), then passed to C.__init__(). The buggy code in C's __init__() results in two separate calls to B's __init__(), for the lone object that has been created. – Peter Hansen Feb 27 '16 at 18:08

2 Answers2

8

The super() just means "next in line", where the line is the mro ['C', 'A', 'B', 'object']. So next in line for A is B.

The mro is calculated according to an algorithm called C3 linearization. When you use super(), Python just goes along this order. When you write your class A you don't know yet which class will be next in line. Only after you create your class C with multiple inheritance and run your program you will get the mro and "know" what will be next for A.

For your example it means:

C() calls the __init__() of C, in which it calls the __init__() of A. Now, A uses super() and finds B in the mro, hence it calls the __init__() of B. Next, the __init__() of C calls the __init__() of B again.

Calling super() in the __init__() creates a different mro and avoids the double call to the __init__() of B.

from __future__ import print_function

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

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

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

Use:

>>> C.mro()
[__main__.C, __main__.A, __main__.B, object]
>> C()
C
A
B
Community
  • 1
  • 1
Mike Müller
  • 82,630
  • 20
  • 166
  • 161
  • 1
    i still don't get it can you explain more – danidee Feb 25 '16 at 12:23
  • C's MRO is `['C', 'A', 'B', 'object']`. C calls `A.__init__()`, A calls `super()`, in C's MRO (Where `A.__init__()` was called) next in line is 'B', so `print 'B'` is called in A's super() and in `B.__init__()` – Mr. E Feb 25 '16 at 12:37
1

Let's modify the code a bit and replace __init__ with doit just to make sure the behavior is generic and not related to __init__.

Let's also add more output to see what exactly happens:

class A(object):
     def doit(self):
         print "A", self, super(A, self)
         super(A, self).doit()

class B(object):
     def doit(self):
         print "B", self, super(B, self)

class C(A,B):
     def doit(self):
         print "C", self
         A.doit(self)
         B.doit(self)

print "MRO:", [x.__name__ for x in C.__mro__]
#MRO: ['C', 'A', 'B', 'object']

C().doit()

This will output:

C <__main__.C object at ...>
A <__main__.C object at ...> <super: <class 'A'>, <C object>>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>

You see, that self is actually C object everywhere, so when you hit the A.doit, you actually have <super: <class 'A'>, <C object>>.

Which translates into:

for the object C call the doit method of the next (super) class after the A from the MRO list

And the next class in MRO after A is B, so we end up calling B.doit().

Check also this code:

class C(A,B):

     def doit_explain(self):
         print "C", self
         # calls B.doit()
         super(A, self).doit()
         print "Back to C"
         # calls A.doit() (and super in A also calls B.doit())
         super(C, self).doit()
         print "Back to C"
         # and just B.doit()
         B.doit(self)

Here instead of A.doit(self) I use super(A, self).doit() directly and it also results in B.doit() call, here is the output:

C <__main__.C object at ...>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>
Back to C
A <__main__.C object at ...> <super: <class 'A'>, <C object>>
B <__main__.C object at ...> <super: <class 'B'>, <C object>>
Back to C
B <__main__.C object at ...> <super: <class 'B'>, <C object>> 
Borys Serebrov
  • 15,636
  • 2
  • 38
  • 54