Python类-多继承和MRO, since 2022-05-1

2022-05-18  本文已影响0人  Mc杰夫

(2022.05.18 Wed)
一个Python类可以继承多个类。继承类过程中的方法解析顺序(method resolution order,即超类中有不同的同名方法时继承的顺序)可由内置的__mro__方法查询。注意,类有该__mro__属性/方法,而实例化的结果没有__mro__。比如下面这个类继承。

class a:
    pass
class b:
    pass
class c:
    pass
class d(a,b):
    pass
class f(c):
    pass
class g(b):
    pass
class k(d, f, g):
    pass
>> k.__mro__
(__main__.k,
 __main__.d,
 __main__.a,
 __main__.f,
 __main__.c,
 __main__.g,
 __main__.b,
 object)

古典类和新式类

在Python 2.x中,类的创建分为古典类和新式类。古典类不从object类继承,新式类继承object类。

# 这是经典类
class ac:
    pass
# 这是新式类
class ac(object):
    pass

在Python 3.x中,取消了古典类,所有类的创建都是新式类,也就是下面三种创建类的方式等价

class ac:
    pass
class ac(object):
    pass
class ac():
    pass

古典类无法使用super()方法,而新式类在调用超类时除了可以用superclassname.method()的方式,还可以用super()方式调用超类方法。

MRO method resolution order

a b c d四个类,呈菱形继承关系,即b->ac->ad->b,c,其定义如下

class a:
    def show(self):
        print('a show')
class b(a):
    pass
class c(a):
    def show(self):
        print('c show')
class d(b, c):
    pass

类继承顺序采用DFS,即从d类开始按照继承类的顺序做深度优先搜索,d \rightarrow b\rightarrow a\rightarrow object \rightarrow c\rightarrow a\rightarrow object。这样就将一个复杂的继承关系转化为一个线性继承关系。在Python 2.2以前的古典类中,继承来自于MRO中第一次出现该超类的位置。这个案例中的类继承顺序是d \rightarrow b \rightarrow a\rightarrow c

这种继承顺序在2.2版本之后被摒弃,仍然是从左到右的顺序,如果调用的方法出现重复类,则只保留该类最后一次出现的位置。考虑上面的案例,考虑到MRO,d \rightarrow b\rightarrow a\rightarrow object \rightarrow c\rightarrow a\rightarrow object,继承顺序是d \rightarrow b \rightarrow c\rightarrow a。可通过__mro__方法查看

>> d.__mro__
(__main__.d, __main__.b, __main__.c, __main__.a, object)

该遍历顺序相当于从DFS结果的尾部开始查看并记录类和超类第一次出现的顺序,如果有重复则跳过,得到一个序列,再将该序列反转即可得到MRO。

调用d类的show方法,查看返回结果

>> d().show()
c show

之所以返回c show是因为根据MRO的调用顺序,d在MRO序列中第一个有show方法定义的位置,即c类,停止,并调用该类中的show方法。而b中虽然继承了show方法但没有显式定义,跳过

merge/C3算法

面对更加复杂的调用顺序,Python 2.2起的新式类引入了merge/C3搜索算法计算MRO。考虑下面这个情况

class d:
    pass
class e:
    pass
class f:
    pass
class b(d, e):
    pass
class c(d, f):
    pass
class a(b, c):
    pass

b\rightarrow d \rightarrow ec\rightarrow d \rightarrow f,根据merge/C3算法决定MRO。

定义:

merge算法过程如下
L[C(B_1...B_N)] = C + merge(L[B_1]...L[B_N], B_1, ..., B_N)
其中merge方法的计算规则如下:在L[B_1]...L[B_N], B_1, ...,B_N中,取L[B_1]的head,如果该元素不在L[B_2]...L[B_N], B_1, ...,B_N的尾部序列中,将该元素从所有列表中删除,否则去L[B_2]的head继续相同的判断。直到列表为空(结束),或没有找到任何符合要求的头元素(引发异常)。

用merge算法计算上面继承结构的MRO
L[o]=o, L(d)=do, L(e)=eo, L(f)=fo

\begin{aligned} L[b] &=b+merge(L[d], L[e]) \\ & = b+merge(do, eo) \\ & = b + d + merge(o, eo)\\ & = b + d + e + merge(o, o)\\ & = b+d+e+o\\ & = bdeo \end{aligned}
\begin{aligned} L[c] &=c+merge(L[d], L[f]) \\ & = c+merge(do, fo) \\ & = c + d + merge(o, fo)\\ & = c + d + f + merge(o, o)\\ & = c+d+f+o\\ & = cdfo \end{aligned}
\begin{aligned} L[a] &=a+merge(L[b], L[c]) \\ & = a+merge(bdeo, cdfo) \\ & = a + b + merge(deo, cdfo)\\ & = a + b + c + merge(deo, dfo)\\ & = a+b+c+d+merge(eo, fo)\\ & = a+b+c+d+e+merge(o, fo)\\ & = a+b+c+d+e+f+merge(o, o)\\ & = a+b+c+d+e+f+o\\ & = abcdefo \end{aligned}
查看__mro__方法

>> a.__mro__
(__main__.a,
 __main__.b,
 __main__.c,
 __main__.d,
 __main__.e,
 __main__.f,
 object)

验证了merge算法的结果。

如果在merge算法执行时出现merge(ab, ba)这种情况,解释器无法知道如何处理这种情况,直接抛出异常。

在类的多继承中,避免出现菱形调用的复杂情况。

Reference

1 编写高质量代码 改善Python程序的91个建议,张颖等著,机械工业出版社

上一篇下一篇

猜你喜欢

热点阅读