How does Python's super () work with multiple inheritance?

Keywords: Python Programming IPython github

I'm a stranger to object-oriented Python programming, especially when it comes to multiple inheritance, and it's hard for me to understand the super() function (the new style class).

For example, if you have something similar:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

What I don't understand is: will the Third() class inherit two constructor methods? If so, which will run with super (), and why?

What if you want to run another one? I know this is related to Python method parsing order( MRO )About.

#1st floor

Your code and other answers are wrong. They lack the super() calls in the first two classes needed to cooperate with subclasses to work properly.

This is a fixed version of the code:

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

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

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

The super() call will find the next method in MRO in each step, which is why First and Second must also have it, otherwise execution will stop at the end of Second. \.

This is what I got:

>>> Third()
second
first
third

#2nd floor

I know it doesn't answer super() directly, but I think it's relevant enough to share.

There is also a way to call each inherited class directly:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Note that if you do this, you have to call each method manually, because I'm pretty sure I won't call First's \.

#3rd floor

Another point that hasn't been covered is passing parameters to initialize the class. Because the destination of super depends on the subclass, the only good way to pass parameters is to package them together. Then be careful not to make the same parameter name have different meanings.

For example:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

give:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

It's tempting to call the superclass ﹐ init ﹐ directly to assign parameters more directly, but if any super calls and / or MrOS in the superclass are changed and class A may be called multiple times (depending on the implementation), it fails.

The conclusion is that cooperative inheritance and super parameters and specific parameters used for initialization can not work well together.

#4th floor

This is how I solve the following problems: multiple inheritance with different variables for initialization and multiple MixIn with the same function call. I had to explicitly add variables to the passed * * kwargs and add a MixIn interface as the endpoint of the super call.

Here A is the extensible base class, and B and C are the mixin classes, both of which provide the function F. Both A and B expect parameter v in their init, while C expects w. Function f takes A parameter y. Q inherits from all three classes. Mixin f is the mixin interface between B and C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

#5th floor

I want to elaborate a little bit Here's the answer, Because when I started reading how to use super () in Python's multi inheritance hierarchy, I didn't get it immediately.

What you need to know is that super (MyClass, self).? init? Provides the next? Init? Method based on the method resolution order (MRO) algorithm used in the complete inheritance hierarchy.

The last part is essential for understanding. Let's consider the example again:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

according to Guido van Rossum Write the order of method parsing Of article Before python 2.3, the "depth first traversal from left to right" was used to calculate the "init" resolution order:

Third --> First --> object --> Second --> object

After removing all duplicates (except the last one), we get:

Third --> First --> Second --> object

So let's track what happens when an instance of the Third class is instantiated, such as x = Third().

  1. Execute according to MRO third.
    • Print Third(): entering
    • Then, execute super (third, self). 65110; init ﹖ and MRO returns first. ﹖ init ﹖ which will be called.
  2. First. Execute.
    • Print First(): entering
    • Then execute super (first, self). (init) and MRO returns second. (init). This will be called.
  3. Second. Execute.
    • Print Second(): entering
    • Then super (second, self). (init) executes, and MRO returns the called object.
  4. Object. Init execute (there is no print statement in the code there)
  5. Execution returns to second. Init, and then displays Second(): exiting
  6. Execution returns to first. Init, then First(): exiting
  7. Execute to return to third. Init, and then display Third(): exiting

This details why instantiating Third () causes:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

The MRO algorithm has been improved from Python 2.3, which can work well in complex situations, but I guess in most cases, you can still use "depth first traversal from left to right" + "delete duplicate expectations" (please comment if not). Be sure to read Guido's blog post!

Posted by ConnorSBB on Wed, 18 Dec 2019 02:15:16 -0800