class DerivecLClassName (Base1, Base2 , Base3)
谈到多继承,下面来讨论一下图所示的菱形继承的经典问题。
假设有上图所示继承关系,当用古典类实现 的时候,如果有实例 d=D()
,
当调用 d.getvalue()
和 d. show()
方法的时候分别对应哪个父类中的方法?
当改为新式类来实现时,结果又将是怎样的呢?看具体实现:
class A():
def getvalue (self):
print ("return value of A")
def show(self):
print ("I can show the information of A")
class B(A):
def getvalue(self):
print("return value of B")
class C (A):
def getvalue(self):
print ("return value of C")
def show(seif):
print ("I can show the information of C")
class D(B,C):pass
当用古典类实现的时候会发现,分別调用的是B类的 getvalue()
方法和A类中的 show()
方法,
而当改为新式类实现(请读者自行验证)的时候,
结果却变为调用B类的 getvalue()
方法和C类的 show()
方法。从两种不同实现方式的输出上也可以证实这一点。
古典类输出如下:
return value of B
I can show the information of A
新式类输出如下:
return value of B
I can show the information of C
为什么两种情况下输出结果会有所不同呢?这背后到底发生了什么?根本原因在哪里? 实际上,导致这些不同点的根本原因在于古典类和新式类之间所采取的MRO ( Method Resolution Order,方法解析顺序) 的实现方式存在差异。
MRO的理解
在古典类中,MRO搜索采用简单的自左至右的深度优先方法,
即按照多继承申明的顺序形成继承树结构,自顶向下采用深度优先的搜索顺序,
当找到所需要的属性或者方法的时 候就停止搜索。因此如上图所示,
当调用 d.getvalu()
的时候,其搜索顺序为 D->B
,
所以调用的是B类中对应的方法,而 d.show()
的搜索顺序为 D->B->A
,
因此最后调用的是A类中对应的方法。
而新式类采用的是 C3MRO
搜索方法,该算法推述如下:
假定,C1C2...CN
表示类 a
到 CN
的序列,其中序列头部元素 (head)=Cl
,
序列尾部 (tail)定义为=C2..CN;
C继承的基类自左向右分别表示为B1,B2...BN;
L[C]
表示C的线性继承关系,其中L[object]=object
。
算法具体过程如下:
L[C (B1 …BN)] = C + merge(L(B1)]…L(BN], B1 …BN)
其中 merge
方法的计算规则如下:在L[B1]...L[BN]
,BL..BN
中,取L[B1]
的 head
,
如果该元素不在L[B2]...L[BN]
,B1...BN
的尾部序列中,则添加该元素到C的线性继承序列中,
间时将该元素从所有列表中删除(该头元素也叫good head),否则取L[B2]
的head。
继续相同的判断,直到整个列表为空或者没有办法找到任何符合要求的头元素(此时将引发一个异常)。
结合上面的例子来说明 C3 MRO
箅法的具体计算方法,以新式类实现的上述菱形继承关系如图6-4所示。
根据算法规则有如下关系表达式:
L(0)=0; L(A)=A0;
则:
L(B) =B+merge(L(A))=B+merge(AO) = B+A+merg(0,0) =B, A, 0
L(C) =C+merge(L(A))=C+merge(AO) = C+A+merge (O,0) =C, A, 0
L(D) =D+merge(L (B) , L(C), BC)
=D+merge(BAO,CAO,BC)
=D+B+merge(A0,CA0,C)(下一个计算取AO的头A.但A包含在CAO的尾部,因此不满足条件.跳到下一个元素CA0继续计算)
=D+B+C+merge(A0,A0)
=D+B+C+A+0 =DBCA0
因此对于上述例子,当D的实例d调用 getvalue()
和 show()
方法时按照D->B-C->A->0的顺序进行搜索,
在第一个找个该方法的位置停止搜索并调用该类对应的方法,因此 getvalue()
会在B的类中找到对应的方法,
而 show
会在 C()
类中找到对应的方法。
关于MRO的搜索顺序也可以在新式类中通过查看__mro__
属性得到证实。D.__mro__
的输出如下:
(<class '__main__.D' >, <class '__main__.B ' >, <class '__main__.C'>, <class '__main__.A'>,<type 'object'>)
实际上MRO虽然叫方法解析顺序,但它不仅是针对方法搜索,对于类中的数据属性也适用。 读者可以自行验证。
根据C3MRO算法的描述,如果找不到满足条件的 good head
,则会摒弃该元素从而对下一个元素进行査找。
但如果找遍了所有的元素都找不到符合条件的 good head
会怎么样呢?来看一个具体例子。
# class A(object):pass
# class B(object):pass
# class C(A,B):pass #基类顺序为A,B
# class D(B,A):pass #基类顺序为B,A
# class E(C,D):pass
运行程序会发现这种情况下有异常抛出。
根据上述代码的继承关系图和MRO算法可以得出;
L(E)=E+merge (L(C) ,L(D), CD)
=E+merge(CABO,CBAO,CD)
=E + C+merge (ABO, BAO, D)
=E+C+D+merge(AB0+BAO)
当算法进行到最后一步的时候便再也找不到满足条件的head 了,因为当选择ABO的头 A元素的时候, 发现其包含在BAO的尾部AO中;同理,B包含在BO中,此时便形成了一个死锁, Python解释器此时不知道如何处理这种情况,便直接抛出异常,这就是上述例子有异常抛出的原因。
菱形继承是在多继承设计的时候需要尽量避免的一个问题。