Python3中类的多继承与MRO顺序

1. 多继承

1.1 什么是多继承

  python是一种面向对象的编程语言,其中重要编程方式之一是 继承 。通过继承,子类便可以扩展父类的功能,实现多态。
  继承有两种形式,一种是单继承,即一个类只继承一个父类;另一种是 多继承,即一个类继承自多个类,将具有多个类的特征。 多继承的语法格式如下:

class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...):
    pass

  当然,子类所继承的所有父类同样也能有自己的父类,同样也可以实现多继承!

Base
Base1
Base2
Base3
Base1.1
Base1.2
Base2.1
Base2.2
Base3.1
Base3.2

1.2 多继承的注意点

  • 多继承很好的模拟了世界,因为事务很少单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。
  • 多继承的实现会导致编译器设计的复杂度增加,所以现在很多语言舍弃了类的多继承,例如C++支持多继承;Java舍弃了多继承但保留了接口(Java的一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口只是方法的声明,继承者必须实现这些方法。)
  • 多继承可能会带来二义性。例如,猫和狗都继承自动物类,现在一个类多继承了猫和狗类,猫和狗都有了shout方法,子类究竟继承谁的shout呢?

2. MRO-方法解析顺序

2.1 MRO概念及查看方式

  方法解析顺序(Method Resolution Order,或MRO) 是在 面向对象编程 中,当某个实例对象应用了继承,进而引发 多态 特性时,编译/解释器 查找并决定具体实例方法的顺序
  根据子类继承的父类数量区分,MRO 会有两种情况:单继承的 MRO 一般比较简单;多继承的 MRO 就复杂很多。
  Python 通过算法计算出每一个类的 MRO 列表。一个类的 MRO 列表是一个包含了其继承链上所有基类的线性顺序列,并且列表中的每一项均保持唯一。当需要在继承链中寻找某个属性时,Python会在 MRO 列表中从左到右开始查找各个基类,直到找到第一个匹配这个属性的类为止。在Python3中可通过如下两种方式查看某个类的MRO列表:

  • 类名.__mro__:该属性用于存储 MRO 元组,以便在方法解析期间提供基类排序。
  • 类名.mro():通过元类(metaclass)可以覆盖此方法,以自定义类实例的方法解析顺序。该方法会在程序初始化时调用,其结果存储在 __mro__ 中。

2.2 MRO的作用

  • 实现方法重载
  • 构建OOP多态
  • 保证继承有效

MRO 是 OOP(面向对象编程)的一根顶梁柱,没了它,OOP 的特性和优势都会大打折扣。

2.3 MRO历史演变

Python的 MRO 顺序目前经历了三个阶段:

  • Python 2.1 及以前版本:针对经典类,基于深度优先算法保留各类首次访问结果
  • Python 2.2 版本:针对新式类,基于深度优先算法保留各类末次访问结果
  • Python 2.3 及以后版本:针对新式类,基于C3 算法的访问结果,也是 Python 3 唯一支持的 MRO顺序。

2.3.1 经典类之保留首次访问结果的深度优先算法

  Python2.1及以前的版本,是经典类时代。经典类是⼀种没有继承object类及其子类的类,实例类型都是type类型,如果经典类被作为父类,子类调用父类的构造函数时会出错,如下所示:

class A:
	def init (self):
		print "A Class Init"
		
class B(A):
	def init_ (self):
		super(B,self).__init__()
		print"B Class Init"

b = B()

运行结果为:

Traceback (most recent call last):
  File "p2super.py",line 11, in <module>
    b=B()
  File "p2super.py", line 7, in .__init__
    super(B,self).__init__
TypeError: super() argument 1 must be type, not classobj

  但不涉及到调用父类构造函数时,经典类的MRO顺序采用 深度优先算法(Depth First Search, 简称 DFS)。深度优先算法的核心思想是:从一个未被访问过的顶点开始,沿当前顶点的边(有向或无向)走到未被访问过的顶点,当没有未被访问过的顶点时,则返回上一个顶点,继续试探访问别的顶点,直到所有顶点都被访问过。深度优先算法访问路径中保留各类首次出现结果的序列即为 经典类的MRO顺序。
  如下例,边的箭头指向父类,此时从A开始根据继承关系沿着有向边方向进行访问,访问的路径为:A→B→D→C→E,对访问路径进行重复类检查调整后的 MRO 顺序为:A B D C E

A
B
C
D
E

Python 代码如下,可通过inspect.getmro(A)可以查看经典类的MRO顺序

import inspect
class E:
	pass
	
class D:
	pass
	
class C(E):
	pass
	
class B(D):
	pass
	
class A(B, C):
	pass
	
if __name__ =='__main__':
	print inspect.getmro(A)

# 输出:(<class __main__.A at 0x10e0e5530>,<class __main__.B at 0x10e0e54c8>,<class __main__.D at 0x10e0e53f8>,<class __main__.C at 0x10e0e5460>,<class __main__.E at 0x10e0e5414>)

  再如下例菱形继承,从A开始根据继承关系沿着有向边方向进行访问,访问的路径为:A→B→D→C→D,对访问路径进行重复类检查调整后的 MRO 顺序为:A B D C

A
B
C
D
import inspect
class D:
    def method(self):
        print("CommonD")
        
class B(D):
    pass
    
class C(D):
    def method(self):
        print("CommonC")
        
class A(B, C):
    pass

inspect.getmro(A)   # 输出:(<class __main__.A at 0x10e0e5530>,<class __main__.B at 0x10e0e54c8>,<class __main__.D at 0x10e0e53f8>,<class __main__.C at 0x10e0e5460>)

  但此种MRO顺序应用在菱形继承时是不合适的!在A的实例中调用method()方法,观察输出结果,

A().method()     # 输出:CommonD

  公共父类D中定义了method(),子类C中重写了method(),按照上述MRO顺序,A的实例调用的是D的method()而非C的。但是一般而言,子类重写的父类方法更具体化,此时的MRO顺序会导致C中的method()永远访问不到,重写的method()失去了意义。

2.3.2 新式类之保留末次访问结果的深度优先算法

  从 Python2.2 开始,为了使类的内置类型更加统一,引入了新式类(new-style class),此时新式类和经典类共存。
  Python2.x(x代表任意 ≥ 2的整数) 的新式类必须显式声明继承自基类object,子类可以调用基类的构造函数。但由于新式类都有一个公共的祖先类object则必会产生上述菱形继承情况,基于深度优先算法保留各类首次访问结果的 MRO 顺序不能满足需求,因此提出了 基于深度优先算法保留各类末次访问结果 的 MRO 顺序。在定义新式类时便计算出该类的 MRO 并将其作为类的 __mro__属性,因此可以直接通过 类名.__mro__类名.mro() 获取类的 MRO。
  此时经典类和新式类共存,因此也有两种MRO并存。如果是经典类,MRO使用基于深度优先算法的保留各类首次访问结果;如果是新式类,MRO使用基于深度优先算法的保留各类末次访问结果。
  采用了基于深度优先算法保留各类末次访问结果的 MRO 顺序后,上述菱形继承的 MRO 顺序为:A B C D,解决了父类的方法覆盖子类重写的方法、子类只能继承不能重写方法的问题。

※ 对于MRO顺序第二阶段采用的算法,有两种说法:一种是基于深度优先算法保留各类末次访问结果;另一种是广度优先算法(BFS),从一个未被访问过的顶点开始,沿当前顶点的边(有向或无向)从左到右进行层次访问。我个人比较偏向第一种说法,原因如下:有如下继承关系(边的箭头指向父类),

A
B
C
D
E
F
object

在 Python 2.2.2 中通过代码进行验证,结果如下:

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

# 输出:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.F'>, <type 'object'>)

  但此种 基于深度优先算法保留各类末次访问结果的MRO顺序 仍然是有违反单调性原则的问题!单调性原则:子类不能改变基类的 MRO 搜索顺序。

如下例:
在这里插入图片描述

class X(object): 
    pass
class Y(object): 
    pass
class A(X, Y): 
    pass
class B(Y, X): 
    pass
class C(A, B): 
    pass

print(C.__mro__)	
print(A.__mro__)	
print(B.__mro__)	

'''输出结果
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class '__main__.Y'>, <type 'object'>)
(<class '__main__.A'>, <class '__main__.X'>, <class '__main__.Y'>, <type 'object'>)
(<class '__main__.B'>, <class '__main__.Y'>, <class '__main__.X'>, <type 'object'>)
'''

  A 和 B 存在二义性的继承关系,在 B 和 C 的MRO顺序中,X、Y 的搜索顺序是相反的。意味着当 B 被继承时,它本身的搜索顺序发生了改变,这违反了单调性原则。
  尽管不创建具有二义性继承关系的类,此时的 MRO 也会有违反 局部优先性 的问题。局部优先性:在继承序列内越靠前的基类,优先级越高。(即对于class C(A, B), C 优先继承 A,其次继承 B
如下例:

G
E
F
object
class F(object): 
    remember2buy ='spam'

class E(F): 
    remember2buy ='eggs'

class G(F, E): 
    pass

print(G.mro())   
print(G.remember2buy) 

"""输出
[<class '__main__.G'>, <class '__main__.E'>, <class '__main__.F'>, <type 'object'>]
eggs
"""  

  G 的 MRO 顺序 “G → E → F → object” 没有违背单调性规则,但是注意到 G 的基类中 F 在左边、E 在右边,根据局部优先性F应优先于E。

2.3.3 新式类之C3算法

  为解决Python 2.2 中新式类 MRO 所存在的问题,从 Python2.3 开始新式类的 MRO 改用C3算,禁止创建Python 2.2 中两种继承关系的类,如若创建则会抛出异常:

>>> class C(A, B): pass
Traceback (most recent call last):
  File "<ipython-input-8-01bae83dc806>", line 1, in <module>
    class C(A, B): pass
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases X, Y

>>> class G(F, E): pass
Traceback (most recent call last):
  File "<ipython-input-8-01bae83dc807>", line 7, in <module>
    class G(F, E): pass
TypeError: Cannot create a consistent method resolution
order (MRO) for bases F, E

C3算法也是 Python 3 唯一支持的 MRO顺序算法,且Python 3中仅存在新式类,所有类默认继承自基类object。

C3 superclass linearization(C3 超类线性化算法,简称C3算法),其本质是一个排序算法,主要用于生成多继承 MRO(方法解析顺序)硬核原论文链接:A monotonic superclass linearization for Dylan (acm.org)

C3算法的名称来源于C3算法的三个特性。MRO顺序中强调类与类的相对顺序满足以下三个特性即可!

  • preservation of local precedence order 局部优先顺序
  • fitting a monotonically criterion 单调性准则
  • a consistent extended precedence graph 拓展优先图

2.4 C3算法

2.4.1 三个特性

2.4.1.1 preservation of local precedence order 局部优先顺序

  局部优先顺序指的是,对于 有直接继承关系 的多个父子和一个子类,在子类的MRO顺序中,父类的访问顺序与代码中定义的父类左右相对顺序一致。
  如下图:class B(D, E, F),局部优先决定了在B类的MRO顺序中D类一定排在E类的前面,E类一定排在F类的前面。

B
D
E
F
2.4.1.2 fitting a monotonically criterion 单调性准则

  单调性指的是某个类的MRO中任意两个类的相对顺序和自己所有父类的MRO里这两个类的相对顺序一致。 这里强调相对顺序而不是相邻顺序
  沿用上例 class B(D, E, F),B的父类D、E、F均默认单继承基类object,MRO顺序依次为:D→object,E→object,F→object。

B
D
E
F
object

  上述仅根据局部优先顺序得到B的部分MRO顺序:B→D→E→F,同时遵循局部优先顺序和单调性准则得到完整MRO顺序为:B→D→E→F→object,B的MRO顺序中遵循了所有父类MRO顺序中各类的相对顺序。

2.4.1.3 a consistent extended precedence graph 拓展优先图

  拓展优先图用于决定一个类的MRO中 所有父类和父类的父类优先级顺序 或 所有父类的父类间的优先级顺序。拓展优先图规定,一个类所有父类的任意两个父类的优先级取决于这两个父类的最小公共子类上,这两个父类本身的优先顺序或这两个父类的子类的优先级顺序。

①对于决定一个类的MRO所有父类和父类的父类优先级顺序的情况,有如下例子:

  如下图 class B(D, E, F)class A(B, C),由上述可知B的MRO顺序为:B→D→E→F→object。

A
B
C
D
E
F
object

现讨论A的MRO顺序,→表示先后相对顺序
局部优先顺序要求:B→C
单调性准则要求:B→D→E→F→object、C→object

要同时满足上述要求,有以下几种组合方式
(1) A→B→C→D→E→F→object
(2) A→B→D→C→E→F→object
(3) A→B→D→E→C→F→object
(4) A→B→D→E→F→C→object

  究竟选择哪一个是最终的A的MRO顺序,此时拓展优先图特性便发挥作用了!
  由上面列举的组合方式可知,多种组合方式是由于B的父类DEF 与 C 的优先顺序无法确定。对A而言,DEF是正是其父类的父类,C是其父类,尝试以分析 D 和 C 的优先级顺序为例,其他同理。
  D 和 C 的最小公共子类是A,由局部优先顺序可知,B优先于C,而B又是D的子类,D的子类优先于C,因此D优先于C。E 和 C、F 和 C 的情况同理,因此,A最终的MRO顺序应为:(4) A→B→D→E→F→C→object

②对于决定一个类的MRO所有父类的父类间的优先级顺序的情况,有如下官方例子:

editable-scrollable-pane
scrollabel-pane
editable-pane
pane
scrolling-mixin
editable-mixin

讨论 editable-scrollable-pane 的MRO顺序,→表示先后相对顺序
局部优先顺序要求:scrollabel-pane → editable-pane
单调性准则要求:scrollabel-pane → pane → scrolling-mixin、editable-pane → pane → editable-mixin
同时满足上述两种要求,有以下两种种组合方式
(1) editable-scrollable-pane → scrollabel-pane → editable-pane → pane → scrolling-mixin → editable-mixin
(2) editable-scrollable-pane → scrollabel-pane → editable-pane → pane → editable-mixin → scrolling-mixin

  由上面列举的组合方式可知,局部优先顺序和单调性准则没办法确定editable-mixin和scrolling-mixin的优先级顺序。根据拓展优先图规定,editable-mixin和scrolling-mixin的最小公共子类是 editable-scrollable-pane,局部优先顺序要求:scrollabel-pane → editable-pane,而 scrollabel-pane 是 scrolling-mixin 的子类,editable-pane 是 editable-mixin的子类,因此scrolling-mixin优先于 editable-mixin,最终editable-scrollable-pane的MRO顺序为:(1) editable-scrollable-pane → scrollabel-pane → editable-pane → pane → scrolling-mixin → editable-mixin

2.4.2 计算方法

  前面介绍了C3算法的三个特性,现在尝试通过数学符号和公式抽象出C3算法的计算方法。符号定义如下:

  • 有顺序的元素集合称为序列 ,记作[]。MRO是一种线性化序列,因此把类C的MRO记作L(C)=[C1,C2,⋯,CN],其中C1,C2,⋯,CN为C的直接父类或间接父类,首项C1为L(C)的 头,记作L(C)head;其余元素C2,⋯,CN为L(C)的尾,记作L(C)tail
  • 规定运算法则:[C]+[C1,C2,⋯,CN]=[C,C1,C2,⋯,CN]
  • 如果一个类C直接继承自基类B1,B2,⋯,BN,那么计算C的MRO公式就是:L(C)=[C]+merge(L(B1),L(B2),⋯,L(BN),[B1,B2,⋯,BN])。merge是一个特殊的序列合并运算,接受多个序列输入,输出一个新的序列。 特别地,对于基类object,L(object)=[object]
  • merge(L(B1),L(B2),⋯,L(BN),[B1,B2,⋯,BN])的运算过程为:
    1. 取首个输入序列L(B1),再取其头L(B1)head,记作 H。
    2. 若 H 未出现在 merge 中除自身所在序列外的其它序列的尾部(在其他序列的尾中不曾出现的头,我们称之为好头),则将其输出,并将其从所有序列中删除,然后回到步骤 1;否则,取出下一个序列的头部记作 H,继续该步骤。
    3. 重复上述步骤,直至merge的序列为空,则算法结束;若merge的序列不为空但不能再找出可以输出的元素则抛出异常。

merge操作流程图如下:
在这里插入图片描述

2.4.2.1 示例一

对上述提到过的例子,基于C3算法计算其MRO。为了merge时方便多个序列的头尾比对,将merge中的多个序列竖排展示
在这里插入图片描述
计算过程如下:
L ( X ) = [ X ] + m e r g e ( L ( o b j e c t ) , [ o b j e c t ] ) = [ X ] + [ o b j e c t ] = [ X , o b j e c t ] \begin{aligned} L(X) =[X] + merge\begin{pmatrix}L(object),\\ [object]\end{pmatrix} = [X] + [object] = [X, object] \end{aligned} L(X)=[X]+merge(L(object),[object])=[X]+[object]=[X,object]
L ( Y ) = [ Y ] + m e r g e ( L ( o b j e c t ) , [ o b j e c t ] ) = [ Y ] + [ o b j e c t ] = [ Y , o b j e c t ] \begin{aligned} L(Y) =[Y] + merge\begin{pmatrix}L(object),\\ [object]\end{pmatrix} = [Y] + [object] = [Y, object] \end{aligned} L(Y)=[Y]+merge(L(object),[object])=[Y]+[object]=[Y,object]
L ( A ) = [ A ] + m e r g e ( L ( X ) , L ( Y ) , [ X , Y ] ) = [ A ] + m e r g e ( [ X , o b j e c t ] , [ Y , o b j e c t ] , [ X , Y ] ) = [ A ] + [ X ] + m e r g e ( [ o b j e c t ] , [ Y , o b j e c t ] , [ Y ] ) = [ A , X ] + [ Y ] + m e r g e ( [ o b j e c t ] , [ o b j e c t ] [ ] ) = [ A , X , Y , o b j e c t ] \begin{aligned} L(A) & =[A] + merge\left(\begin{array}{l}L(X),\\L(Y),\\ [X,Y]\end{array}\right) \\& = [A] + merge\left(\begin{array}{l}[X, object] ,\\ [Y, object] ,\\ [X,Y]\end{array}\right) \\& = [A] + [X] +merge\left(\begin{array}{l}[object] ,\\ [Y, object] ,\\ [Y]\end{array}\right) \\& = [A,X] + [Y]+merge\left(\begin{array}{l}[object] ,\\ [object] \\ []\end{array}\right) \\& = [A,X,Y,object] \end{aligned} L(A)=[A]+merge L(X),L(Y),[X,Y] =[A]+merge [X,object],[Y,object],[X,Y] =[A]+[X]+merge [object],[Y,object],[Y] =[A,X]+[Y]+merge [object],[object][] =[A,X,Y,object]
L ( B ) = [ B ] + m e r g e ( L ( Y ) , L ( X ) , [ Y , X ] ) = [ B ] + m e r g e ( [ Y , o b j e c t ] , [ X , o b j e c t ] , [ Y , X ] ) = [ B ] + [ Y ] + m e r g e ( [ o b j e c t ] , [ X , o b j e c t ] , [ X ] ) = [ B , Y ] + [ X ] + m e r g e ( [ o b j e c t ] , [ o b j e c t ] [ ] ) = [ B , Y , X , o b j e c t ] \begin{aligned} L(B) & =[B] + merge\left(\begin{array}{l}L(Y),\\L(X),\\ [Y,X]\end{array}\right) \\& = [B] + merge\left(\begin{array}{l}[Y, object] ,\\ [X, object] ,\\ [Y,X]\end{array}\right) \\& = [B] + [Y] +merge\left(\begin{array}{l}[object] ,\\ [X, object] ,\\ [X]\end{array}\right) \\& = [B,Y] + [X]+merge\left(\begin{array}{l}[object] ,\\ [object] \\ []\end{array}\right) \\& = [B,Y,X,object] \end{aligned} L(B)=[B]+merge L(Y),L(X),[Y,X] =[B]+merge [Y,object],[X,object],[Y,X] =[B]+[Y]+merge [object],[X,object],[X] =[B,Y]+[X]+merge [object],[object][] =[B,Y,X,object]

L ( C ) = [ C ] + m e r g e ( L ( A ) , L ( B ) , [ A , B ] ) = [ C ] + m e r g e ( [ A , X , Y , o b j e c t ] , [ B , Y , X , o b j e c t ] , [ A , B ] ) = [ C ] + [ A ] + m e r g e ( [ X , Y , o b j e c t ] , [ B , Y , X , o b j e c t ] , [ B ] ) = [ C , A ] + [ B ] + m e r g e ( [ X , Y , o b j e c t ] , [ Y , X , o b j e c t ] [ ] ) \begin{aligned} L(C) & =[C] + merge\left(\begin{array}{l}L(A),\\L(B),\\ [A,B]\end{array}\right) \\& = [C] + merge\left(\begin{array}{l} [A,X,Y,object],\\ [B,Y,X,object],\\ [A,B]\end{array}\right) \\& = [C] + [A] +merge\left(\begin{array}{l}[X,Y,object],\\ [B,Y,X,object],\\ [B]\end{array}\right) \\& = [C,A] + [B]+merge\left(\begin{array}{l}[X,Y,object] ,\\ [Y,X,object] \\ []\end{array}\right) \end{aligned} L(C)=[C]+merge L(A),L(B),[A,B] =[C]+merge [A,X,Y,object],[B,Y,X,object],[A,B] =[C]+[A]+merge [X,Y,object],[B,Y,X,object],[B] =[C,A]+[B]+merge [X,Y,object],[Y,X,object][]
  自此步骤无法继续向下计算!当检测merge中第一个序列[X,Y,object] 的头 X 是否出现在其他序列的的尾时,发现其出现在了第二个序列[[Y,X,object] 的尾[X,object] 中,因此暂时跳过第一个序列。检测merge中第二个序列的头Y是否出现在其他序列的的尾时,发现其出现在了第一个序列[X,Y,object] 的尾[Y,object] 中,此时merge已无未被检测过的序列,此次计算失败!

相类似的,对上述提到过的另一个例子,基于C3算法计算其MRO。

G
E
F
object

计算过程如下:
L ( F ) = [ F ] + m e r g e ( L ( o b j e c t ) , [ o b j e c t ] ) = [ F ] + [ o b j e c t ] = [ F , o b j e c t ] \begin{aligned} L(F) =[F] + merge\begin{pmatrix}L(object),\\ [object]\end{pmatrix} = [F] + [object] = [F, object] \end{aligned} L(F)=[F]+merge(L(object),[object])=[F]+[object]=[F,object]

L ( E ) = [ E ] + m e r g e ( L ( F ) , [ F ] ) = [ E ] + m e r g e ( [ F , o b j e c t ] , [ F ] ) = [ E ] + [ F ] + m e r g e ( [ o b j e c t ] , [ o b j e c t ] ) = [ E , F , o b j e c t ] \begin{aligned} L(E) & =[E] + merge\left(\begin{array}{l}L(F),\\ [F]\end{array}\right) \\& = [E] + merge\left(\begin{array}{l}[F, object],\\ [F]\end{array}\right) \\& = [E] + [F] +merge\left(\begin{array}{l}[object] ,\\ [object]\end{array}\right) \\& = [E,F,object] \end{aligned} L(E)=[E]+merge(L(F),[F])=[E]+merge([F,object],[F])=[E]+[F]+merge([object],[object])=[E,F,object]

L ( G ) = [ G ] + m e r g e ( L ( F ) , L ( E ) , [ F , E ] ) = [ G ] + m e r g e ( [ F , o b j e c t ] , [ E , F , o b j e c t ] , [ F , E ] ) \begin{aligned} L(G) & =[G] + merge\left(\begin{array}{l}L(F),\\L(E),\\ [F,E]\end{array}\right) \\& = [G] + merge\left(\begin{array}{l}[F, object] ,\\ [E,F, object] ,\\ [F,E]\end{array}\right) \end{aligned} L(G)=[G]+merge L(F),L(E),[F,E] =[G]+merge [F,object],[E,F,object],[F,E]
  自此步骤无法继续向下计算!当检测merge中第一个序列[F, object] 的头 F 是否出现在其他序列的的尾时,发现其出现在了第二个序列[E,F, object] 的尾[F, object] 中,因此暂时跳过第一个序列。检测merge中第二个序列的头E是否出现在其他序列的的尾时,发现其出现在了第三个序列[F, E] 的尾[E] 中,因此暂时跳过第二个序列。检测merge中第三个序列的头F是否出现在其他序列的的尾时,发现其出现在了第二个序列[E,F, object] 的尾[F, object] 中。此时merge已无未被检测过的序列,此次计算失败!

2.4.2.2 示例二

现在来尝试计算一个符合C3三个特性的继承关系的MRO顺序。

A
B
C
D
E
F
object

代码如下:

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

print(A.mro())

#[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>,<class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>]

计算过程如下:
L ( D ) = [ D ] + m e r g e ( L ( o b j e c t ) , [ o b j e c t ] ) = [ D ] + [ o b j e c t ] = [ D , o b j e c t ] \begin{aligned} L(D) =[D] + merge\begin{pmatrix}L(object),\\ [object]\end{pmatrix} = [D] + [object] = [D, object] \end{aligned} L(D)=[D]+merge(L(object),[object])=[D]+[object]=[D,object]
L ( E ) = [ E ] + m e r g e ( L ( o b j e c t ) , [ o b j e c t ] ) = [ E ] + [ o b j e c t ] = [ E , o b j e c t ] \begin{aligned} L(E) =[E] + merge\begin{pmatrix}L(object),\\ [object]\end{pmatrix} = [E] + [object] = [E, object] \end{aligned} L(E)=[E]+merge(L(object),[object])=[E]+[object]=[E,object]
L ( F ) = [ F ] + m e r g e ( L ( o b j e c t ) , [ o b j e c t ] ) = [ F ] + [ o b j e c t ] = [ F , o b j e c t ] \begin{aligned} L(F) =[F] + merge\begin{pmatrix}L(object),\\ [object]\end{pmatrix} = [F] + [object] = [F, object] \end{aligned} L(F)=[F]+merge(L(object),[object])=[F]+[object]=[F,object]

L ( B ) = [ B ] + m e r g e ( L ( D ) , L ( E ) , [ D , E ] ) = [ B ] + m e r g e ( [ D , o b j e c t ] , [ E , o b j e c t ] , [ D , E ] ) = [ B ] + [ D ] + m e r g e ( [ o b j e c t ] , [ E , o b j e c t ] , [ E ] ) = [ B , D ] + [ E ] + m e r g e ( [ o b j e c t ] , [ o b j e c t ] [ ] ) = [ B , D , E , o b j e c t ] \begin{aligned} L(B) & =[B] + merge\left(\begin{array}{l}L(D),\\L(E),\\ [D,E]\end{array}\right) \\& = [B] + merge\left(\begin{array}{l}[D, object] ,\\ [E, object] ,\\ [D,E]\end{array}\right) \\& = [B] + [D] +merge\left(\begin{array}{l}[object] ,\\ [E, object] ,\\ [E]\end{array}\right) \\& = [B,D] + [E]+merge\left(\begin{array}{l}[object] ,\\ [object] \\ []\end{array}\right) \\& = [B,D,E,object] \end{aligned} L(B)=[B]+merge L(D),L(E),[D,E] =[B]+merge [D,object],[E,object],[D,E] =[B]+[D]+merge [object],[E,object],[E] =[B,D]+[E]+merge [object],[object][] =[B,D,E,object]

L ( C ) = [ C ] + m e r g e ( L ( D ) , L ( F ) , [ D , F ] ) = [ C ] + m e r g e ( [ D , o b j e c t ] , [ F , o b j e c t ] , [ D , F ] ) = [ C ] + [ D ] + m e r g e ( [ o b j e c t ] , [ F , o b j e c t ] , [ F ] ) = [ C , D ] + [ F ] + m e r g e ( [ o b j e c t ] , [ o b j e c t ] [ ] ) = [ C , D , F , o b j e c t ] \begin{aligned} L(C) & =[C] + merge\left(\begin{array}{l}L(D),\\L(F),\\ [D,F]\end{array}\right) \\& = [C] + merge\left(\begin{array}{l}[D, object] ,\\ [F, object] ,\\ [D,F]\end{array}\right) \\& = [C] + [D] +merge\left(\begin{array}{l}[object] ,\\ [F, object] ,\\ [F]\end{array}\right) \\& = [C,D] + [F]+merge\left(\begin{array}{l}[object] ,\\ [object] \\ []\end{array}\right) \\& = [C,D,F,object] \end{aligned} L(C)=[C]+merge L(D),L(F),[D,F] =[C]+merge [D,object],[F,object],[D,F] =[C]+[D]+merge [object],[F,object],[F] =[C,D]+[F]+merge [object],[object][] =[C,D,F,object]

L ( A ) = [ A ] + m e r g e ( L ( B ) , L ( C ) , [ B , C ] ) = [ A ] + m e r g e ( [ B , D , E , o b j e c t ] , [ C , D , F , o b j e c t ] , [ B , C ] ) = [ A ] + [ B ] + m e r g e ( [ D , E , o b j e c t ] , [ C , D , F , o b j e c t ] , [ C ] ) = [ A , B ] + [ C ] + m e r g e ( [ D , E , o b j e c t ] , [ D , F , o b j e c t ] [ ] ) = [ A , B , C ] + [ D ] + m e r g e ( [ E , o b j e c t ] , [ F , o b j e c t ] [ ] ) = [ A , B , C , D ] + [ E ] + m e r g e ( [ o b j e c t ] , [ F , o b j e c t ] [ ] ) = [ A , B , C , D , E ] + [ F ] + m e r g e ( [ o b j e c t ] , [ o b j e c t ] [ ] ) = [ A , B , C , D , E , F , o b j e c t ] \begin{aligned} L(A) & =[A] + merge\left(\begin{array}{l}L(B),\\L(C),\\ [B,C]\end{array}\right) \\& = [A] + merge\left(\begin{array}{l} [B,D,E,object],\\ [C,D,F,object],\\ [B,C]\end{array}\right) \\& = [A] + [B] +merge\left(\begin{array}{l}[D,E,object],\\ [C,D,F,object],\\ [C]\end{array}\right) \\& = [A,B] + [C]+merge\left(\begin{array}{l}[D,E,object] ,\\ [D,F,object] \\ []\end{array}\right) \\& = [A,B,C] + [D]+merge\left(\begin{array}{l}[E,object] ,\\ [F,object] \\ []\end{array}\right) \\& = [A,B,C,D] + [E]+merge\left(\begin{array}{l}[object] ,\\ [F,object] \\ []\end{array}\right) \\& = [A,B,C,D,E] + [F]+merge\left(\begin{array}{l}[object] ,\\ [object] \\ []\end{array}\right) \\& = [A,B,C,D,E,F,object] \end{aligned} L(A)=[A]+merge L(B),L(C),[B,C] =[A]+merge [B,D,E,object],[C,D,F,object],[B,C] =[A]+[B]+merge [D,E,object],[C,D,F,object],[C] =[A,B]+[C]+merge [D,E,object],[D,F,object][] =[A,B,C]+[D]+merge [E,object],[F,object][] =[A,B,C,D]+[E]+merge [object],[F,object][] =[A,B,C,D,E]+[F]+merge [object],[object][] =[A,B,C,D,E,F,object]

3. 多继承的意义之Mixin

3.1 引出Mixin类

  因为多继承会导致继承路径复杂,团队协作开发时代码不可控,所以不管编程语言是否支持多继承,都应当避免多继承。但是在特定的场合和情况下,多继承仍然是有意义的!
  有如下继承关系,文档Document类是其他所有文档类的抽象类,Word、Pdf类是Document的子类,现在需要为Document的子类提供打印功能。

Word
Document
Pdf
class Document:
    def __init__(self,content):
        self.content = content
 
class Word(Document): pass
class Pdf(Document):  pass

  “在Document类中添加print方法以供子类使用”的方式显然是不合适的,因为未必适合子类,因此子类中仍需要覆盖重写。且不是所有的Document的子类都需要的,“在子类中添加对应print方法”的方式显然也是不合适的,因为违反了OCP的原则。

单继承方式

  尝试使用单继承原有子类、创建有print方法的类的方式。以子类Word为例,子类Pdf同理,如下所示:

class Document: #不允许修改
    def __init__(self,content):
        self.content = content
        
class Word(Document): pass #不允许修改
class Pdf(Document): pass #不允许修改

class PrinttableWord(Word):    
    def print(self):
        print("PrinttableWord print {}".format(self.content))

a = PrinttableWord("tom com")
a.print()   # 输出:PrinttableWord print tom com

  单继承方式貌似解决了上述问题,但是现实情况并不单一,一个类往往被要求有多个功能。比如除了打印功能外,还要求Document的子类具有可编辑、可修改、可阅读等功能,采用单继承方式需要层层继承实现,如下所示:
ps:仅描述继承关系,省略具体方法实现。

class Print&EdittableWord(PrinttableWord):    
    def edit(self):
        pass

class Print&Edit&ModifytableWord(Print&EdittableWord):    
    def modify(self):
        pass
        
class Print&Edit&Modify&ReadtableWord(Print&Edit&ModifytableWord):    
    def read(self):
        pass
     
......

  这样下来,类的数量会呈指数增长,很明显这样设计是不行的!不管是Document类哪些子类需要上述功能,都需要通过这样单继承实现,同一功能的代码复用率很低。

多继承方式
  分别定义有可打印、可编辑、可修改、可阅读方法的类,Document类哪些子类需要上述功能便多继承这些类,仍旧以子类Word为例,如下所示:

class printable(object):    
    def print(self):
        pass   
class editable(object):    
    def edit(self):
        pass       
class modifiable(object):    
    def modify(self):
        pass
class readable(object):    
    def read(self):
        pass
class NewWord(printable, modifiable, readable, Word):
	pass

  像这样的,实现了某种功能单元,用于被其他子类继承,将功能组合到子类中的类就是 Mixin类。为了提高代码的可读性,通常 Mixin类的类名后缀为 MixIn,如上述 printable 应为 printableMixin
  Mixin 即 Mix-in,常被译为“混入”,是一种编程模式,设计思想。从设计模式的角度来说,多组合,少继承。目的是通过多继承的方式组合多个Mixin类,给一个类带来这些类的的功能。如此能够提高代码复用率、合理组织代码结构。
  在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的单继承关系。

3.2 Mixin的特点

  Mixin本质上是一种特殊的多种继承,强调拓展子类的功能,类似于Java接口的特性,不影响子类主要功能,子类也不能依赖 Mixin(如果是依赖关系,则是真正的基类,不应该用 Mixin 命名。)

为了代码的可读性和可维护性,定义和使用 Mixin 类应该遵循几个原则:

  • Mixin类中不应该显式地出现__init__初始化方法,自身不能进行实例化,实现目的仅用于被子类继承,混入某种功能。
  • 单个Mixin类一般只实现一种功能,可按需继承。
  • 在Minin中一般不要使用super。
  • Mixin类的祖先类也应该是Mixin类。
  • 当多个类都实现了同一种功能时,这时应该考虑将该功能抽离成 Mixin 类。
  • 多继承时,Mixin类通常在继承列表第一个位置。

Mixin优点

  1. Mixin的类功能单一具体,混入之后,新类的MRO顺序相对很简单,并不会引起混乱,没有多重继承的复杂语义,为多重继承提供了一种机制。
  2. Mixin设计模式可以在不对已存在的类进行修改的前提下,扩展此类的功能。
  3. 提高代码复用性:在不同类之间共享功能时,Mixin 将通用功能组合成一个混合,然后继承到需要它的每个类中,无需重复相同的代码。
  4. 可以根据开发需要任意调整功能。

Mixin缺点
5. 受继承关系限制,推荐只有两层的继承使用。
6. Mixin毕竟是多继承方式混入,一定程度上污染新定义的类,如若滥用,有时候会带来MRO的问题。

3.3 Mixin的应用

  在Python中,很多自带的库也使用了MixIn。例如,SocketServer模块的UDPServer类和TCPServer类,分别作为UDP和TCP套接字服务器的服务器类提供网络服务。当同时服务多个用户时必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
  比如,编写一个多线程模式的TCP服务,定义如下:

class ThreadingTCPServer(ThreadingMixIn, TCPServer):
	pass

  ThreadingMixIn类向TCP服务器添加功能,以便每个新连接创建一个新线程。或者,使用ForkingMixIn为每个新连接分叉进程。
  显然,创建新线程或分支进程的功能作为独立类是非常有用的!Mixins提供了额外的功能,而不影响套接字服务器的功能。

  在很多框架比如Django普遍用到了Mixin这种模式,定义api或者viewset的时候就能够通过多重继承的方式复用一些功能。

class TemplateView(TemplateResponseMixin, ContextMixin, View):
    """
    A view that renders a template.  This view will also pass into the context
    any keyword arguments passed by the URLconf.
    """
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

class TemplateResponseMixin(object):
    """
    A mixin that can be used to render a template.
    """
    template_name = None
    template_engine = None
    response_class = TemplateResponse
    content_type = None

    def render_to_response(self, context, **response_kwargs):
        """
        Returns a response, using the `response_class` for this
        view, with a template rendered with the given context.

        If any keyword arguments are provided, they will be
        passed to the constructor of the response class.
        """
        response_kwargs.setdefault('content_type', self.content_type)
        return self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            **response_kwargs
        )

    def get_template_names(self):
        """
        Returns a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response is overridden.
        """
        if self.template_name is None:
            raise ImproperlyConfigured(
                "TemplateResponseMixin requires either a definition of "
                "'template_name' or an implementation of 'get_template_names()'")
        else:
            return [self.template_name]

class ContextMixin(object):
    """
    A default context mixin that passes the keyword arguments received by
    get_context_data as the template context.
    """

    def get_context_data(self, **kwargs):
        if 'view' not in kwargs:
            kwargs['view'] = self
        return kwargs

  可以看到这TemplateResponseMixin、ContextMixin实际上给TemplatesView提供get_context_data和render_to_response两个接口以及其他需要的数据。

4. 补充

4.1 MRO与super的关系

  很多人认为super()是类中调用其父类的方法的一个函数,如此理解是有偏差的,观察下例:

class Adef say(self):
		print("A")

class Bdef say(self):
		super().say()

class C(A)def say(self):
		print("C")

class M(B, C)def say(self):
		B.say(self)

m = M()
m.say()

# 输出结果: C

  与直观感受相反的,在M中显式调用B的say方法,B的say方法并没有调用其父类A的say方法,而是调用了与B毫无关系的C的say方法!那super到底是什么?其工作原理又是什么?
  super是一个Python的一个内置类型,类似列表和字典,被定义在Python源码typeobject.c的Pysuper_Type中。因此,super()不是一个函数或方法,而是创建了一个super对象。

创建super对象

  • 语法:super ( [ type [, object-or-type] ] )
  • 参数:Python2.x 以前必须传入两个参数,第一个参数是一个type,第二个参数可以是一个type或一个object。从Python3开始,在类定义中使用时,参数可选,形式为 super()。此时super()会自动将自身所在类传入super ([type [, object-or-type]])第一个参数中,将自身所在函数的第一个参数传入super ([type [, object-or-type]])的第二个参数中。
  • 工作原理:在第二个参数的类的MRO列表上搜索第一个参数的下一个类,检测这个类有无super()后调用的方法或属性,若有,将此方法或属性绑定到第二个参数上;若无,继续在第二个参数的MRO列表上寻找这个类的下一个类,重复以上检测步骤。

  定义在B的实例方法say()中的super().say()等价于super(B, self).say(),在M的实例方法say()中执行B.say(self)时,此时self是M的实例被传入到了super(B, self).say()的self中。super(B, self)会查找M的MRO顺序(M→B→C→A→object)中B的下一个类即C,M的实例调用则是C的实例方法say()。实际上,以下两段代码等价。

class M(B, C)def say(self):
		B.say(self)
class M(B, C)def say(self):
		super().say()

参考

1.Python3中的C3算法:多继承查找规则
2.详细总结Python类的多继承知识
3.Python MRO方法解析顺序详解
4.多重继承 - 廖雪峰的官方网站
5.https://www.cnblogs.com/xiao-xue-di/p/14472769.html#_label1
6.【python】B站最细致的super()详解,一定有你不知道的知识!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值