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

6
  • Why are you not using super in C's init method? Commented Feb 25, 2016 at 12:14
  • 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. Commented Feb 25, 2016 at 12:18
  • 1
    See Raymond Hettinger's talk on super, might help make more sense: youtube.com/watch?v=EiOglTERPEo Commented Feb 25, 2016 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. Commented Feb 25, 2016 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. Commented Feb 27, 2016 at 18:08

2 Answers 2

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
Sign up to request clarification or add additional context in comments.

2 Comments

i still don't get it can you explain more
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__()
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>> 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.