11.1 方法解析顺序
方法解析顺序(Method Resolution Order, MRO)定义了 Python 在类层次结构中寻找方法和属性的顺序。在多重继承中,这尤其重要,因为一个类可能会从多个父类中继承属性和方法。
简单来说,有一个严格固定的顺序(或者说是算法),Python 按照这个算法遍历类继承树。这个算法确保了正确的方法查找顺序,大致如下:
C3线性化算法
C3线性化算法通过结合以下内容确定 MRO:
- 类本身。
- 父类按照声明顺序的列表。
- 同样顺序的父类的 MRO。
C3线性化算法规则
-
保持方法的局部顺序:如果类
A
被声明在类B
之前,那么类A
的所有方法都应在类B
的方法之前查看。 -
维持父类的顺序:如果类
A
是类B
的父类,那么类A
的所有方法都应在类B
的方法之前查看。 -
考虑继承的顺序:如果类
C
是两个或多个类的父类,类C
的方法顺序应在这些类的 MRO 中保持不变。
算法步骤:
步骤 1. 从类本身开始:
总是从调用方法的类本身开始。
步骤 2. 添加基类按照它们声明的顺序:
在当前类之后,按照继承时指定的顺序检查基类。
步骤 3. 遍历父类:
在那里寻找字段和方法。
步骤 4. 结合父类的 MRO:
如果同一个基类通过多个路径继承,它只会被检查一次,并且是按照正确的顺序(其它所有时候会被跳过)。
对于已经了解“算法与数据结构”的人来说,这是一种深度优先搜索,而不是广度优先搜索。
11.2 检查 MRO
在 Python 中,可以使用属性 __mro__
或函数 mro()
检查类的方法和字段的遍历顺序。
例子:
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
def method(self):
print("D")
# 检查 MRO
print(D.__mro__)
输出将是:
(<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<class 'object'>)
这显示了 Python 将寻找方法和属性的顺序:
D
:Python 首先在类D
中查找方法。-
B
:然后 Python 查找类B
(第一个父类)中的方法。 -
C
:如果类B
中没有找到方法,Python 将在类C
(第二个父类)中查找方法。 -
A
:如果在类B
和C
中都没有找到方法,Python 将在类A
中查找方法。 -
object
:最后,Python 在基类object
中查找方法。
11.3 使用 super()
与 MRO
函数 super()
遵循 MRO 来按正确的顺序调用父类的方法。来看一下如何使用 super()
:
class A:
def method(self):
print("A")
super().method()
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
d = D()
d.method()
输出将是:
D
B
C
A
遍历顺序 (MRO)
1. 调用类 D
的方法 method
:
- Python 首先在类
D
中查找到方法。 - 执行方法
D.method()
并打印"D"
。 -
然后调用
super().method()
,它按照 MRO 调用下一个方法。
2. 调用类 B
的方法 method
:
- 根据 MRO,紧随
D
之后的类是B
。 - 执行方法
B.method()
并打印"B"
。 -
然后调用
super().method()
,它按照 MRO 调用下一个方法。
3. 调用类 C
的方法 method
:
- MRO 中紧随
B
之后的类是C
。 - 执行方法
C.method()
并打印"C"
。 -
然后调用
super().method()
,它按照 MRO 调用下一个方法。
4. 调用类 A
的方法 method
:
- MRO 中紧随
C
之后的类是A
。 - 执行方法
A.method()
并打印"A"
。 -
然后调用
super().method()
,但由于A
没有父类的method
(除了object
),调用将结束而没有进一步的操作。
GO TO FULL VERSION