Most of the inheritance mentioned earlier is single inheritance, where a subclass has only one parent class, but Python also supports multiple inheritance, meaning that a subclass can have multiple parents. Multiple inheritance has complex parent class conflicts, and most object-oriented languages only support single inheritance. Python is one of the few languages that supports multiple inheritance, and this article will explore this topic.
Multiple Inherited Grammar Structures
The syntax structure of multi inheritance is generally as follows:
class SubClassName(BaseClass1,BaseClass2,…):
def __init__(self, *args):
…
Of course, all parent classes inherited by subclasses can also have their own parent classes, which can result in an inheritance relationship mechanism diagram as shown in the following figure:

The parent class may be complex, but in multi inheritance, it is difficult to handle the diamond shaped inheritance structure:

We use examples to analyze the situation of multiple inheritance:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base2):
pass
g = Grand()
g.foo()
'’'
Base1.__init__
Base2.__init__
Base.__init__
Base1.foo
'''
From the above example, it can be seen that:
- Grandchild's__Init__Method called two parent objects__Init__Method, but only called the grandfather object once__Init__method
- The foo method of the grandchild object only called the foo method of the first parent object, and did not call the foo methods of the second parent object and grandfather object.
Even cross generational mixed inheritance is possible:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base):
pass
g = Grand()
g.foo()
'’’
Base1.__init__
Base.__init__
Base1.foo
’'’
But there are also forbidden areas where the interpreter cannot distinguish which parent class method should be used:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base, Base2):
pass
g = Grand()
g.foo()
'’’
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Base, Base2
’'’
MRO
MRO (Method Resolution Order) is also known as Method Resolution Order. You can query the parsing order of classes through type(). micro().
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
class Base3(Base):
def __init__(self):
print(f'Base3.__init__')
super().__init__()
def foo(self):
print(f'Base3.foo')
class Base31(Base1, Base2):
pass
class Base32(Base2, Base3):
pass
class Grand(Base32, Base31):
pass
g = Grand()
g.foo()
print(Grand.mro())
'’’
Base1.__init__
Base2.__init__
Base3.__init__
Base.__init__
Base1.foo
[<class '__main__.Grand'>, <class '__main__.Base32'>, <class '__main__.Base31'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base3'>, <class '__main__.Base'>, <class 'object'>]
’'’
For programming languages that only support single inheritance, MRO is simple, starting from the current class and searching for its parent class one by one. For multi inheritance, MRO is relatively more complex. Since its development, Python has gone through the following three MRO algorithms:
- From left to right, using the Depth First Search (DFS) algorithm, known as the Old Class MRO;
- Starting from Python version 2.2, the new class has been optimized based on the use of depth first search algorithm;
- Since Python version 2.3, the C3 algorithm has been used for new classes. Due to Python 3. x only supporting modern classes, this version only uses the C3 algorithm.
Old style class MRO algorithm
When using the MRO algorithm for legacy classes, take the following code as an example:
class A:
def method(self):
print("CommonA")
class B(A):
pass
class C(A):
def method(self):
print("CommonC")
class D(B, C):
pass
D().method()
Through analysis, it can be inferred that the four classes in this program have a "diamond" inheritance relationship. When using the D-class object to access the method() method, according to the depth first algorithm, the search order is D->B->A->C->A.
The MRO algorithm using the old class first searched for the method() method in base class A. In Python 2. x version, the running result of this program is:
CommonA.
However, this result is clearly not what we want, what we want to search for is the method() method in Class C.
New class MRO algorithm
To solve the problems of the old class MRO algorithm, Python version 2.2 has introduced a new method for calculating the new class MRO. It still uses depth first traversal from left to right, but if there are duplicate classes in the traversal, only the last one is retained.
Taking the above program as an example, through depth first traversal, the search order is D->B->A->C->A. Since there are two A's in this order, only the latter one is retained, and the final search order obtained after simplification is D->B->C->A.
Modern classes can be directly named by class name__MRO__The MRO of a class can also be obtained by using the class name. mro(). Old style classes do not have __mro__ properties and mro() methods.
As can be seen, this MRO approach can already solve the problem of diamond inheritance, but it may violate the principle of monotonicity. The so-called monotonicity principle refers to the fact that when there are multiple inheritances in a class, subclasses cannot change the MRO search order of the base class, otherwise it will cause exceptions in the program.
For example:
class X(object):
pass
class Y(object):
pass
class A(X,Y):
pass
class B(Y,X):
pass
class C(A, B):
pass
By conducting a deep traversal, the search order is obtained as C->A->X->object->Y->object->B->Y->object->X->object, and then simplified (using the latter for the same) to obtain C->A->B->Y->X->object.
Let's analyze whether this search order is reasonable. Let's take a look at the MRO in each class:
- For A, its search order is A> X -> Y> Object;
- For B, its search order is B -> Y> X -> Object;
- For C, its search order is C> A -> B -> X -> Y> Object.
It can be seen that the search order of X and Y in B and C is opposite, which means that when B is inherited, its search order changes, which violates the monotonicity principle.
MRO C3
To address the issues with MRO in Python 2.2, Python 2.3 uses the C3 method to determine the order of method parsing. In most cases, if someone mentions MRO in Python, they are referring to the C3 algorithm. The C3 algorithm solves the problems of monotonicity and the inability to inherit and rewrite. The specific MRO C3 algorithm is quite complex (The Python 2.3 Method Resolution Order | Python. org), so it will not be described here.
Super()
The built-in function super() in Python is used to call a method of the parent class (superclass) to solve the problem of multiple inheritance. Not only can the constructor of the parent class be called, but also the member functions of the parent class can be called.
The super() built-in function (object) returns a proxy object (temporary object of the superclass), allowing us to access the methods of the base class.
class Person(object):
def eat(self, times):
print(f'i eat every day{ times }meals.')
class Student(Person):
def eat(self):
#call superclass
super().eat(4)
tom = Student()
tom.eat()
#i eat four meals a day.
Student.mro()
# [__main__.Student, __main__.Person, object]
Calling parent class initialization inheritance:
class Base(object):
def __init__(self, a, b):
self.a = a
self.b = b
class A(Base):
def __init__(self, a, b, c):
super().__init__(a, b)
#super(A, self).__init__(a, b) # Python 2 writing methods
self.c = c
a = A(1,2,3)
A.mro()
# [__main__.A, __main__.Base, object]
Grammar
It is actually a built-in class, with the following syntax:
class super(self, /, *args, **kwargs)
# or
super(type[, object-or-type])
Return a proxy object that will call the method Delegate to parent or sibling classes of type . This is useful for accessing inheritance methods that have already been overloaded in the class. However, it should be noted that the parent or sibling class specified by type is being called, and the specific class to be called depends on the order of MRO. In other words, it is actually the next class of the class specified by type on the MRO list being called, as shown in the following example:
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base2):
def __init__(self):
print(f'Grand.__init__')
super().__init__()
def foo(self):
print(f'Grand.foo')
super(Base1, self).foo()
g = Grand()
g.foo()
print(Grand.mro())
'’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
[<class '__main__.Grand'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base'>, <class 'object'>]
'''
The result printed by the foo() function shocked you because what you wrote was.
Super (Base1, self). foo().
But the actual output is.
Base2. foo.
And Base2 and Base1 actually have no direct relationship, how could they call the foo of Base2?.
It's because super() doesn't even look at the inheritance relationship of Base1, but rather at Grand's MRO, as you can see, On the order of Grand's MRO Base2 is the next class of Base1, and super (Base1, self). foo() calls the foo() method of the next class of Base1.
Note: If the second parameter is omitted, the returned superclass object is unbound. If the second parameter is an object,
isinstance(obj, type)must be a true value. If the second parameter is of a type,issubclass(type2, type)must be true (this applies to class methods).
In addition to method lookup, super() can also be used for property lookup. One possible application is to call the descriptor (any object that defines __get__(), __set__() or __delete__() methods) in a parent or peer class
Please note that super() is implemented as part of the binding process of explicitly adding dot attribute lookup, such as super()__Getitem__(name). It achieves this by implementing its own__Getattribute__The () method allows for searching for classes in a predictable order and supports collaborative multiple inheritance. Correspondingly, super() is not defined when using statements or operators for implicit lookup like super() [name]
It should also be noted that super() is not limited to internal use within methods, except in the form of zero parameters. The form of two parameters clearly specifies the parameters and provides corresponding references. The zero parameter form is only applicable within the class definition, as the compiler needs to fill in the necessary details to correctly retrieve the defined class, and also needs to allow ordinary methods to access the current instance.
import os
class Base():
def __init__(self):
print(f'Base.__init__')
def foo(self):
print(f'Base.foo')
class Base1(Base):
def __init__(self):
print(f'Base1.__init__')
super().__init__()
def foo(self):
print(f'Base1.foo')
class Base2(Base):
def __init__(self):
print(f'Base2.__init__')
super().__init__()
self.__nicename__= 'Base2'
def foo(self):
print(f'Base2.foo')
pass
class Grand(Base1, Base2):
def __init__(self):
print(f'Grand.__init__')
super().__init__()
def foo(self):
print(f'Grand.foo')
super(Base1, self).foo()
print('__getattribute__:', super().__getattribute__('__nicename__'))
g = Grand()
g.foo()
print(Grand.mro())
'’'
Grand.__init__
Base1.__init__
Base2.__init__
Base.__init__
Grand.foo
Base2.foo
__getattribute__: Base2
[<class '__main__.Grand'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.Base'>, <class 'object'>]
'''