An in depth analysis of multiple inheritance of Python classes

  • 2020-04-02 14:19:11
  • OfStack

The body of the

The first thing to note is that Python classes are divided into classic and new classes
The classic class was something before python2.2, but is still compatible in 2.7, but only new classes are recognized in versions after 3
The new class is available in python2.2 and later

The difference between the classic class and the new class is as follows:

The classic class is not derived from a base class by default, while the new class is derived from the base class object by default:


# old style
class A():pass # new style
class A(obejct):pass

2. The classical class adopts the depth-first matching method from left to right when the class multiple inheritance. The new classes are matched using C3 (different from breadth first)

3. The classic class is not called with the instance.mro(), but the new class is.

Why not switch to a new class instead of a classic class

Because there are some problems with multiple inheritance in classic classes... May cause a method query in the inheritance tree to bypass the following parent class:


class A():
    def foo1(self):
        print "A"
class B(A):
    def foo2(self):
        pass
class C(A):
    def foo1(self):
        print "C"
class D(B, C):
    pass d = D()
d.foo1()

According to the rule of depth-first from left to right in the search order of classic classes, when accessing d.oo1 (), the D class is not available.. So look up, find B first, there is no depth first, access A, find foo1(), so this is calling foo1() of A, which causes foo1() of C to be bypassed.

So python introduced the concept of new classes, each base class inherited from object and his matching rules were changed from depth-first to C3

C3 algorithm

How does the C3 algorithm match... After discussing the question and answer section above, it boils down to the following:

At the heart of the C3 algorithm is merge.

In the merge list, if the first class of the first sequence mro appears in another sequence and is the first or no other sequence, the class is removed from those sequences and merged into the access order list
For example :(quote from zhuangzebo's answer in the question @zhuangzebo)


class A(O):pass
class B(O):pass
class C(O):pass
class D(A,B):pass
class E(C,D):pass

First of all, we need to know that the mro(method resolution order) list of O(object) is [O,].
So what's next:


mro(A) = [A, O]
mro(B) = [B, O]
mro(C) = [C, O]
mro(D) = [D] + merge(mro(A), mro(B), [A, B])
= [D] + merge([A, O], [B, O], [A, B])
= [D, A] + merge([O], [B, O], [B])
= [D, A, B] + merge([O], [O])
= [D, A, B, O]
mro(E) = [E] + merge(mro(C), mro(D), [C, D])
= [E] + merge([C, O], [D, A, B, O], [C, D])
= [E, C] + merge([O], [D, A, B, O], [D])
= [E, C, D] + merge([O], [A, B, O])
= [E, C, D, A, B] + merge([O], [O])
= [E, C, D, A, B, O]

And then there's a special case:
Such as:
Merge (DO,CO,C), merge D first
Merge (DO,CO,C), merge first is C
This means that when a class appears at the head of two sequences (such as C) and the class only appears at the head of one sequence (such as D), it matches sequentially.

The access sequence generated by the new class is stored in a read-only list called MRO..
You can use instance.arbitration or instance.mro()

The final match is made in the order of the MRO sequence

The difference between C3 and breadth first:

Here's an example that makes sense:


class A(object):pass
class B(A):pass
class C(B):pass
class D(A):pass
class E(D):pass
class F(C, E):pass

According to breadth-first traversal, the MRO sequence of F should be [F,C,E,B,D,A].
But C3 is [F,E,D,C,B,A]
What that means is that you can think of C3 as going deep over one link to the intersection with another link, and then going deep over the other link, and finally traversing the intersection

Super and access by class name for new and classic classes

In the classic class, if you want to access the parent class, you access it with the class name..


class A():
    def __init__(self):
        print "A"
class B(A):
    def __init__(self):
        print "B"
        A.__init__(self)  #python The parent class initialization function is not called by default

This may seem like a good idea, but if the inheritance structure of the class is complex, the code will be less maintainable.
So the new class introduced super...


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

At this point, there is another question: when the class is multiple inheritance, which class does super access ??
Super actually determines which class to access by means of the s... This is actually a method that calls one of the classes following this class in arbitration.
For example, if the sequence is [F,E,D,C,B,A] then super in F is E, and E is D

Super and access by class name to the pit from the mix


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

And it says:


 enter F
 enter E
 enter B
 leave B
 enter C
 enter D
 enter A
 leave A
 leave D
 leave C
 leave E
 enter D
 enter A
 leave A
 leave D
 leave F

And you can see that the initializers of D and A got messed up twice!
Accessing by class name is the equivalent of the GOTO statement before C... Jump around and then use super to access.. There's a problem

So it is recommended that you either always use super or always use access by class name

Best realization:

Avoid multiple inheritance
Consistent use of super
Don't mix classic with new
Be careful to check the class hierarchy when calling the parent class

Above is my understanding of python class inheritance, I hope to help you


Related articles: