Python的多重继承允许一个子类同时继承多个父类,这种设计可以复用多个父类的功能,但也带来了方法查找的歧义问题。方法解析顺序(MRO)就是Python用来确定多重继承场景下方法、属性查找顺序的规则,它保证了方法调用的确定性和可预测性。

什么是MRO
MRO全称为Method Resolution Order,即方法解析顺序,是Python解释器在调用类的方法或访问类的属性时,按照特定顺序遍历继承链的规则。当子类调用一个方法时,如果子类本身没有定义这个方法,解释器就会按照MRO的顺序依次在父类中查找,直到找到第一个匹配的方法为止。
在Python3中,MRO采用的是C3线性化算法,这个算法保证了继承顺序的单调性,避免了经典类时代的方法查找歧义问题。
如何查看类的MRO
每个Python类都有一个__mro__属性,它是一个元组,里面按照MRO的顺序存放了当前类以及所有父类的引用,我们可以通过这个属性直接查看类的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方法")
# 定义子类D,同时继承B和C
class D(B, C):
pass
# 查看D类的MRO
print(D.__mro__)
# 调用D实例的show方法
d = D()
d.show()
运行上述代码,输出结果如下:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) B的show方法
从输出可以看到,D类的MRO顺序是D -> B -> C -> A -> object,所以调用show方法时,会先在B类中找到对应的方法并执行,不会继续往后查找。
MRO的计算规则
Python3的MRO基于C3线性化算法,计算规则可以总结为:子类优先于父类,继承顺序靠前的父类优先于靠后的父类,同一个类在MRO中只会出现一次。
对于复杂的继承结构,我们可以通过C3算法的公式来手动计算MRO,公式如下:
MRO(类) = [类] + merge(父类的MRO列表,父类的继承顺序列表)
merge操作的规则是:
- 从第一个列表的第一个元素开始,如果这个元素不在其他任何列表的尾部(除了第一个列表本身),就把它放到输出列表,并从所有列表中删除这个元素
- 如果元素在其他列表的尾部,就跳过当前列表,检查下一个列表的第一个元素,重复上述操作
- 直到所有列表都被清空,merge操作完成
下面通过一个菱形继承的例子来演示计算过程:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
计算D的MRO:
- 首先列出各个类的父类MRO:
- MRO(A) = [A, object]
- MRO(B) = [B] + merge([A, object], [A]) = [B, A, object]
- MRO(C) = [C] + merge([A, object], [A]) = [C, A, object]
- 计算MRO(D) = [D] + merge(MRO(B), MRO(C), [B, C])
- merge的三个列表分别是[B, A, object]、[C, A, object]、[B, C]
- 第一个列表的第一个元素是B,不在其他列表的尾部,取出B,剩余列表:[A, object]、[C, A, object]、[C]
- 第一个列表的第一个元素是A,在第二个列表的尾部(第二个列表尾部是object,A不在尾部?不对,重新看:第二个列表是[C,A,object],A的后面是object,所以A不在第二个列表的尾部?哦不对,尾部是指列表的最后一个元素吗?不,merge规则里的尾部是指除了第一个元素之外的其他元素?不对,正确的C3 merge规则是:对于要取出的元素x,x不能出现在其他列表的除第一个位置之外的位置。哦刚才的规则描述有误,正确的规则是:取第一个列表的头元素,如果这个元素没有出现在其他列表的除第一个位置之外的位置,就把它拿出来,否则就取下一个列表的头元素。
- 重新来:merge的三个列表L1=[B,A,object], L2=[C,A,object], L3=[B,C]
- 取L1的头B,检查B是否在L2的除第一个位置之外的位置?L2除第一个位置是[A,object],没有B;L3除第一个位置是[C],没有B,所以取出B,现在结果=[D,B],剩余L1=[A,object], L2=[C,A,object], L3=[C]
- 取L1的头A,检查A是否在L2除第一个位置之外?L2除第一个是[A,object],有A,所以不取A,看L2的头C,检查C是否在L1除第一个之外?L1除第一个是[object],没有C;L3除第一个之外是空,所以取出C,结果=[D,B,C],剩余L1=[A,object], L2=[A,object], L3=[]
- 取L1的头A,检查A是否在L2除第一个之外?L2除第一个是[object],没有A,取出A,结果=[D,B,C,A],剩余L1=[object], L2=[object]
- 取L1的头object,取出,结果=[D,B,C,A,object],剩余列表都为空,merge完成。
所以D的MRO就是[D,B,C,A,object],和我们用__mro__查看到的结果一致。
super函数与MRO的关系
super()函数是Python中用来调用父类方法的常用函数,它的调用逻辑完全依赖于当前类的MRO顺序。super(当前类, self).方法名()的含义是:按照MRO顺序,从当前类的下一个类开始查找对应的方法。
下面修改之前的D类示例,在B和C的show方法中用super调用父类的方法:
class A:
def show(self):
print("A的show方法")
class B(A):
def show(self):
print("B的show方法")
# 调用MRO中B的下一个类的show方法
super().show()
class C(A):
def show(self):
print("C的show方法")
# 调用MRO中C的下一个类的show方法
super().show()
class D(B, C):
def show(self):
print("D的show方法")
super().show()
d = D()
d.show()
运行上述代码,输出结果如下:
D的show方法 B的show方法 C的show方法 A的show方法
结合D的MRO顺序[D,B,C,A,object],可以看到super()的调用完全按照MRO的顺序执行:D的show调用B的show,B的show调用C的show,C的show调用A的show,符合MRO的顺序规则。
MRO使用的注意事项
在实际开发中使用多重继承和MRO时,需要注意以下几点:
- 尽量避免过于复杂的多重继承结构,复杂的继承链会让MRO难以理解,增加代码维护成本
- 如果多个父类有同名方法,要明确MRO的查找顺序,避免方法调用不符合预期
- 使用
super()时,要清楚当前类的MRO顺序,避免递归调用或者调用到不期望的方法 - 如果不需要多重继承的方法查找规则,可以在子类中显式重写方法,或者指定调用某个父类的方法,比如
父类名.方法名(self)的形式,这种方式不依赖MRO顺序
总结
MRO是Python多重继承的核心规则,它基于C3线性化算法,保证了方法查找的顺序性和可预测性。我们可以通过类的__mro__属性查看具体的MRO顺序,super()函数的调用逻辑也完全依赖MRO。掌握MRO的计算规则和使用场景,可以帮助我们更好地使用多重继承,避免继承带来的方法冲突问题。在实际开发中,建议优先使用组合而非多重继承来实现功能复用,如果必须使用多重继承,要保持继承结构的简洁,明确MRO顺序。