(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->a
,c->a
,d->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
类开始按照继承类的顺序做深度优先搜索,。这样就将一个复杂的继承关系转化为一个线性继承关系。在Python 2.2以前的古典类中,继承来自于MRO中第一次出现该超类的位置。这个案例中的类继承顺序是。
这种继承顺序在2.2版本之后被摒弃,仍然是从左到右的顺序,如果调用的方法出现重复类,则只保留该类最后一次出现的位置。考虑上面的案例,考虑到MRO,,继承顺序是。可通过__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
有,,根据merge/C3算法决定MRO。
定义:
- 表示的线性继承关系,如,,这里的表示从到 object
- 表示类到的序列,该序列头部元素,尾部定义为
- 继承的基类自左向右分别表示为
merge算法过程如下
其中merge方法的计算规则如下:在中,取的head,如果该元素不在的尾部序列中,将该元素从所有列表中删除,否则去的head继续相同的判断。直到列表为空(结束),或没有找到任何符合要求的头元素(引发异常)。
用merge算法计算上面继承结构的MRO
有
查看__mro__
方法
>> a.__mro__
(__main__.a,
__main__.b,
__main__.c,
__main__.d,
__main__.e,
__main__.f,
object)
验证了merge算法的结果。
如果在merge算法执行时出现这种情况,解释器无法知道如何处理这种情况,直接抛出异常。
在类的多继承中,避免出现菱形调用的复杂情况。
Reference
1 编写高质量代码 改善Python程序的91个建议,张颖等著,机械工业出版社
网友评论