python 执行顺序
什么是 MRO?
MRO (方法解析顺序) 是 Python 用来在多继承情况下决定方法查找顺序的规则。它定义了解决方法或属性时的搜索顺序——即在类继承关系中先搜索哪个类的定义。
- MRO 遵循 C3 线性化算法(也叫 C3 Superclass Linearization)。
- 它始终优先查找:
- 当前类。
- 直接父类(从左到右)。
- 间接父类(基于 C3 算法计算)。
你可以通过 ClassName.__mro__
或 inspect.getmro(ClassName)
查看类的 MRO。
基本示例
class A:def show(self):print("A show")class B(A):def show(self):print("B show")class C(A):def show(self):print("C show")class D(B, C): # D 继承 B 和 Cpassd = D()
d.show()
分析:
- 类
D
继承了B
和C
,而且B
和C
都继承了A
。 D
的 MRO 是什么?通过D.__mro__
查看:
输出:print(D.__mro__)
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
- 当调用
d.show()
时,Python 按照 MRO 顺序查找方法:- 首先在类
D
中查找,但类D
没有定义show
方法。 - 接着查找
B
类,发现B
定义了show
方法,于是执行B
的show
,输出:B show
- 首先在类
更复杂的继承关系
让我们看一个更加复杂的多继承例子。
class X:def show(self):print("X show")class Y(X):def show(self):print("Y show")class Z(X):passclass P(Y, Z): # P 继承 Y 和 Zpassp = P()
p.show()print(P.__mro__) # 查看 MRO
分析:
- 类
P
继承了Y
和Z
,而Y
和Z
又都继承了X
。 P
的 MRO 是啥?通过print(P.__mro__)
可以查看:(<class '__main__.P'>, <class '__main__.Y'>, <class '__main__.Z'>, <class '__main__.X'>, <class 'object'>)
- 当调用
p.show()
时,Python 遵循 MRO:- 先查找
P
,发现没有show
方法。 - 接着查找
Y
,找到Y
中的show
方法,执行输出:Y show
- 先查找
为什么遵循 C3 线性化算法?
Python 使用 C3 线性化算法 是为了确保:
- 继承的顺序一致性:类的顺序不能随便更改,保证了 “左优先”、父类优先。
- 单一继承路径:即在搜索属性和方法时,某个类不会被重复访问。
示例:解释 MRO 的计算
让我们看一个更加复杂的例子来理解 MRO 的计算规则:
class A: # 顶层基类passclass B(A):passclass C(A):passclass D(B, C): # 多继承passprint(D.__mro__) # 输出 MRO
分析:
为了计算 D
的 MRO,C3 线性化算法会按以下步骤进行:
- 从
D
开始,将所有直接基类合并,优先选第一个未使用的类。- 类
D
继承了B
和C
,因此顺序为:D -> B -> C
。
- 类
- 再往上层,
B
和C
的父类都是A
,而A
的 MRO 是[A, object]
。 - 最终计算完成后,
D
的 MRO 是:(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
总结图解:
MRO 按如下层级组织:
D
|--> B
| |--> A
|
|--> C|--> A
其中,公共父类 A
存在于 B
和 C
中,但只出现一次。
实例->类->父类(实例属性优先,类属性次之)
class Vector:typecode = 'd' # 表示数组存储的元素类型为双精度浮点数def **init**(self, components): """构造函数,接受任意可迭代对象作为 components"""self._components = array(self.typecode, components)
在 Python 中,当我们通过 self.typecode
访问一个属性时,Python 会按照以下规则逐层查找该属性:
-
实例的属性字典 (
__dict__
):首先,访问实例自己的属性。也就是说,Python 会检查当前实例是否有typecode
这个属性。如果实例中找到了,就会直接返回。
例如:v = Vector([1.0, 2.0, 3.0]) v.typecode = 'f' # 如果我们给实例设置了新的 `typecode`,它会优先被使用
-
类的属性:如果实例中没有找到
typecode
,Python 会去类中查找是否声明了typecode
。
在你的代码中,类的typecode
属性被声明为类变量:typecode = 'd'
-
父类的属性:如果在类中也没有找到,Python 会继续向父类(基类)中查找。这种机制是 Python 的方法解析顺序(MRO,Method Resolution Order)。
-
抛出
AttributeError
异常:如果以上步骤都未找到,则会抛出AttributeError
。
在你提到的例子中:
self.typecode
这个过程是:
- 首先检查实例变量中是否存在一个名为
typecode
的变量。如果没有: - 再检查当前类中是否有
typecode
。在这里,它会发现typecode = 'd'
,于是返回该值。
因此,这个查找流程和机制解释了为什么会从实例开始,然后逐步向类属性查找逻辑。
你也可以通过以下代码验证这个流程:
class Vector:typecode = 'd' # 类变量def __init__(self, components):self._components = components# 测试代码
v = Vector([1.0, 2.0, 3.0])
print(v.typecode) # 打印类变量 'd'v.typecode = 'f' # 修改实例的 typecode
print(v.typecode) # 打印实例变量 'f'
print(Vector.typecode) # 打印类变量,仍然是 'd'
运行结果会是:
d
f
d
这说明:
- 修改
v.typecode
只修改了实例的typecode
,并未影响类的typecode
。 - 当实例中没有
typecode
时,会回退到类中查找。
总结:Python 是动态语言,类变量和实例变量之间通过这种查找顺序建立了一个灵活的机制。