python学习笔记之多继承,MRO算法及MIXIN
多继承
先来看一个多继承的例子,如下:
1 | class A: |
1 | c = C() |
1 | c.method_from_a() |
method of a
1 | c.method_from_b() |
method of b
通过上边的代码可知,python是支持多继承的,父类的方法都能继承到子类,如果父类的方法名是相同时,子类调用这个方法时到底是继承哪个父类的方法呢?如下:
1 | class A1: |
1 | c1 = C1() |
1 | c1.method() |
method of a1
从上边的输出结果来看,子类C1是继承了A1类中的method方法。如果在定义C1类时,把继承的A1和B1两个类的顺序调换后呢?如下:
1 | class C2(B1,A1): |
1 | c2 = C2() |
1 | c2.method() |
method of b1
这样就是继承了B1类中的method方法了。
通过上边的测试,能不能说明在多继承时,子类继承方法总是在定义子类时写在前一个父类中的方法呢?答案是,这样说是不准确的。再看以下的例子:
1 | class A2: |
1 | class C3(A2,B2): |
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-16-5035382970b1> in <module>()
----> 1 class C3(A2,B2):
2 pass
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A2, B2
1 | class C3(B2,A2): |
1 | c3 = C3() |
1 | c3.method() |
method of b2
上边代码中,B2类继承了A2类,B2类中也有一个method方法,当定义C3类时,让其继承(A2,B2)时,直接抛出了异常,但让其继承(B2,A2)时又能正常工作,所以在多继承中,子类继承的方法并不总是继承父类列表(如(A2,B2))左边父类的方法。这又是为什么呢?
这里是存在一个算法存在的,叫MRO算法,全称:method resolution order(方法解析顺序)
MRO
MRO 是通过C3算法计算出来的
MRO遵循以下规则:
-
本地优先级: 根据声明顺序从左往右查找
-
单调性: 所有子类中,也应满足其查找顺序
MRO中采用了C3 算法,而C3算法中重点是一个merge函数,那先来看一个执行merge时步骤:
-
顺序遍历列表
-
查找首元素满足以下条件,否则遍历下一个序列
- 在其他序列也是首元素
- 或者在其他序列里不存在
-
从所有序列中移除此元素,合并到MRO序列中
-
重复执行,直到所有序列为空或无法执行下去
以例子来说明一下C3算法是怎样工作的,比如有一个B类,默认是继承object这个父类的,如下:
1 | class B(object): --> mro(B) = [B,O] |
如果B类继承了A1这个类呢,如下:
1 | class B(A1): --> mro(B) = [B] + merge(mro(A1),[A1]) |
如果B类继承了A1,A2类呢,如下:
1 | class B(A1,A2): --> mro(B) = [B] + merge(mro(A1),mro(A2),[A1,A2]) |
所以:
1 | class B(A1,A2,...) --> mro(B) = [B] + merge(mro(A1),mro(A2),...,[A1,A2,...]) |
以上边定义class C1(A1,B1):
这个类来说明一下为什么执行c1.method()
时输出的是"method of a1",如下:
1 | class A1: |
通过C3算法最后得到了[C1,A1,B1,O]
这样一个序列,这个序列表示C1(A1,B1):
这个类被实例化后,当调用父类中同名方法时的查找顺序,比如调用c1.method()
方法时,解释器先会在"C1"类中查找是否有这个方法,如果没有再去"A1"类中查找,如果没有再去"B1"类中查找,如果还是没有则去"object"这个类中查找,只要有一个类中查找到了该方法,则会执行。
再来看一下C1(B1,A1):
这个类的查找顺序,如下:
1 | class A1: |
最后得到的[C1,B1,A1,O]
序列的查找顺序也符合之前的调用结果。
接下来看一个略复杂一点的例子,如下:
1 | class A2: |
那来看一下class C3(B2,A2):
呢?如下:
1 | class A2: |
得出的结果[C3,B2,A2,O]
也正是方法的查找顺序,这也验证了前边在执行c3.method()
时执行了父类"B2"中的method
方法。
其实在一个类中有一个__mro__
属性,可显示出查找顺序,如下:
1 | C3.__mro__ |
(__main__.C3, __main__.B2, __main__.A2, object)
MIXIN - 混入
MIXIN是一种组合的表现,在python是通过多继承来实现组合。
试想有这样一个场景,我们有一个文档类,文档分为word文档和excel文档,这些文档可以输出到显示器上,也可以输出到打印机上,我们用MIXIN的方式来实现,如下:
1 | class Document: |
上边定义了三个类,Document
类作为Word
和Excel
类的父类,两个子类分别实现了各自的format方法。这两种文档能输出到打印机和显示器上,所以接下来定义这两个类,如下:
1 | class Monitor: |
Monitor
和Printer
类只继承object类,各自实现了输出功能。如果此时我们想把word文档输出到显示器上、word文档输出到打印机上,或者是想把excel文档输出到显示器、excel文档输出到打印机上,应该怎样实现呢?如下:
1 | class WordWithMonitor(Monitor,Word): |
1 | wwm = WordWithMonitor('mix in') |
1 | wwm.format() |
1 | wwm.display() |
i am word,my content is mix in on monitor
1 | wwp = WordWithPrinter('mix in') |
1 | wwp.format() |
1 | wwp.display() |
i am word,my content is mix in on printer
ExcelWithMonitor
类和ExcelWithPrinter
类实例化出来后效果和上边一样,这样通过继承不同的类组合就可以满足不同的要求,上边的Monitor
类和Printer
类我们叫做MINXIN类,这种类有以下特征:
-
此类一般只包含方法,不包含数据
-
此类不能单独实例化,比如上边的
Monitor
类和Printer
,类中没有content
这个变量,所以不能单独实例化 -
此类一般只继承object类,或继承具有MIXIN类特性的类
在使用MINXIN这种方式时,一般会把MIXIN类写在继承列表的首位,如class WordWithMonitor(Monitor,Word):
,这样能避免因其他类的同名方法覆盖MIXIN类的方法,可以通过__mro__
属性来看一下类继承后的查找顺序,如下:
1 | WordWithMonitor.__mro__ |
(__main__.WordWithMonitor,
__main__.Monitor,
__main__.Word,
__main__.Document,
object)
当调用wwm.display()
时,解释器会按WordWithMonitor
类、Monitor
类、Word
类、Document
类这样的顺序查找是否有display
方法,如果在定义WordWithMonitor
类时继承列表中MIXIN类不在首位,如下:
1 | class WordWithMonitor(Word,Monitor): |
那mro的查找顺序是这样的:
1 | WordWithMonitor.__mro__ |
(__main__.WordWithMonitor,
__main__.Word,
__main__.Document,
__main__.Monitor,
object)
如果此时在Word
类或Document
类中也有一个display
方法,那Monitor
类中的display
方法就不能执行,所以说MIXIN类应定义在继承列表的首位。