python面向对象的多继承-MIXIN

python学习笔记之多继承,MRO算法及MIXIN

多继承

先来看一个多继承的例子,如下:

1
2
3
4
5
6
7
8
9
10
class A:
def method_from_a(self):
print('method of a')

class B:
def method_from_b(self):
print('method of b')

class C(A,B):
pass
1
c = C()
1
c.method_from_a()
method of a
1
c.method_from_b()
method of b

通过上边的代码可知,python是支持多继承的,父类的方法都能继承到子类,如果父类的方法名是相同时,子类调用这个方法时到底是继承哪个父类的方法呢?如下:

1
2
3
4
5
6
7
8
9
10
class A1:
def method(self):
print('method of a1')

class B1:
def method(self):
print('method of b1')

class C1(A1,B1):
pass
1
c1 = C1()
1
c1.method()
method of a1

从上边的输出结果来看,子类C1是继承了A1类中的method方法。如果在定义C1类时,把继承的A1和B1两个类的顺序调换后呢?如下:

1
2
class C2(B1,A1):
pass
1
c2 = C2()
1
c2.method()
method of b1

这样就是继承了B1类中的method方法了。

通过上边的测试,能不能说明在多继承时,子类继承方法总是在定义子类时写在前一个父类中的方法呢?答案是,这样说是不准确的。再看以下的例子:

1
2
3
4
5
6
7
class A2:
def method(self):
print('method of a2')

class B2(A2):
def method(self):
print('method of b2')
1
2
class C3(A2,B2):
pass
---------------------------------------------------------------------------

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
2
class C3(B2,A2):
pass
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遵循以下规则:

  1. 本地优先级: 根据声明顺序从左往右查找

  2. 单调性: 所有子类中,也应满足其查找顺序

MRO中采用了C3 算法,而C3算法中重点是一个merge函数,那先来看一个执行merge时步骤:

  1. 顺序遍历列表

  2. 查找首元素满足以下条件,否则遍历下一个序列

  • 在其他序列也是首元素
  • 或者在其他序列里不存在
  1. 从所有序列中移除此元素,合并到MRO序列中

  2. 重复执行,直到所有序列为空或无法执行下去

以例子来说明一下C3算法是怎样工作的,比如有一个B类,默认是继承object这个父类的,如下:

1
2
3
class B(object): --> mro(B) = [B,O]

[B,O]中的字母O表示的就是object

如果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
2
3
4
5
6
7
8
class A1:
class B1:
class C1(A1,B1):
mro(C1) = [C1] + merge(mro(A1),mro(B1),[A1,B1])
= [C1] + merge([A1,O],[B1,O],[A1,B1])
= [C1,A1] + merge([O],[B1,O],[B1])
= [C1,A1,B1] + merge([O],[O])
= [C1,A1,B1,O]

通过C3算法最后得到了[C1,A1,B1,O]这样一个序列,这个序列表示C1(A1,B1):这个类被实例化后,当调用父类中同名方法时的查找顺序,比如调用c1.method()方法时,解释器先会在"C1"类中查找是否有这个方法,如果没有再去"A1"类中查找,如果没有再去"B1"类中查找,如果还是没有则去"object"这个类中查找,只要有一个类中查找到了该方法,则会执行。

再来看一下C1(B1,A1):这个类的查找顺序,如下:

1
2
3
4
5
6
7
8
class A1:
class B1:
class C1(B1,A1):
mro(C1) = [C1] + merge(mro(B1),mro(A1),[B1,A1])
= [C1] + merge([B1,O],[A1,O],[B1,A1])
= [C1,B1] + merge([O],[A1,O],[A1])
= [C1,B1,A1] + merge([O],[O])
= [C1,B1,A1,O]

最后得到的[C1,B1,A1,O]序列的查找顺序也符合之前的调用结果。

接下来看一个略复杂一点的例子,如下:

1
2
3
4
5
6
7
8
9
class A2:
class B2(A2):
class C3(A2,B2):
mro(C3) = [C3] + merge(mro(A2),mro(B2),[A2,B2])
= [C3] + merge([A2,O],([B2] + merge(mro(A2),[A2]),[A2,B2])
= [C3] + merge([A2,O],([B2] + merge([A2,O],[A2])),[A2,B2])
= [C3] + merge([A2,O],([B2,A2] + merge([O])),[A2,B2])
= [C3] + merge([A2,O],[B2,A2,O],[A2,B2])
计算到这里没有任何一个元素符合mro的计算规则,所以就抛出“TypeError”

那来看一下class C3(B2,A2):呢?如下:

1
2
3
4
5
6
7
8
9
10
11
class A2:
class B2(A2):
class C3(B2,A2):
mro(C3) = [C3] + merge(mro(B2),mro(A2),[B2,A2])
= [C3] + merge(([B2] + merge(mro(A2),[A2])),[A2,O],[B2,A2])
= [C3] + merge(([B2] + merge([A2,O],[A2])),[A2,O],[B2,A2])
= [C3] + merge(([B2,A2] + merge([O])),[A2,O],[B2,A2])
= [C3] + merge([B2,A2,O],[A2,O],[B2,A2])
= [C3,B2] + merge([A2,O],[A2,O],[A2])
= [C3,B2,A2] + merge([O],[O])
= [C3,B2,A2,O]

得出的结果[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
2
3
4
5
6
7
8
9
10
11
12
13
class Document:
def __init__(self,content):
self.content = content


class Word(Document):
def format(self):
self.content = 'i am word,my content is {0}'.format(self.content)


class Excel(Document):
def format(self):
self.content = 'i am excel,my content is {0}'.format(self.content)

上边定义了三个类,Document类作为WordExcel类的父类,两个子类分别实现了各自的format方法。这两种文档能输出到打印机和显示器上,所以接下来定义这两个类,如下:

1
2
3
4
5
6
7
class Monitor:
def display(self):
print('{0} on monitor'.format(self.content))

class Printer:
def display(self):
print('{0} on printer'.format(self.content))

MonitorPrinter类只继承object类,各自实现了输出功能。如果此时我们想把word文档输出到显示器上、word文档输出到打印机上,或者是想把excel文档输出到显示器、excel文档输出到打印机上,应该怎样实现呢?如下:

1
2
3
4
5
6
7
8
9
10
11
class WordWithMonitor(Monitor,Word):
pass

class ExcelWithMonitor(Monitor,Excel):
pass

class WordWithPrinter(Printer,Word):
pass

class ExcelWithPrinter(Printer,Excel):
pass
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类,这种类有以下特征:

  1. 此类一般只包含方法,不包含数据

  2. 此类不能单独实例化,比如上边的Monitor类和Printer,类中没有content这个变量,所以不能单独实例化

  3. 此类一般只继承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
2
class WordWithMonitor(Word,Monitor):
pass

那mro的查找顺序是这样的:

1
WordWithMonitor.__mro__
(__main__.WordWithMonitor,
 __main__.Word,
 __main__.Document,
 __main__.Monitor,
 object)

如果此时在Word类或Document类中也有一个display方法,那Monitor类中的display方法就不能执行,所以说MIXIN类应定义在继承列表的首位。

文章目录
  1. 1. 多继承
    1. 1.1. MRO
    2. 1.2. MIXIN - 混入
|