Python 在继承上的设计既灵活又讲究规则,理解单继承、多继承以及方法解析顺序(MRO)是正确使用面向对象和避免踩坑的关键。
单继承
单继承就是一个子类只从一个父类派生,逻辑清晰、控制直接。
class Animal:
def speak(self):
return "动物叫声"
class Dog(Animal):
def speak(self):
return "汪汪"
d = Dog()
print(d.speak()) # 汪汪
- 子类可以复用父类的方法和属性,也可以覆盖(重写)它们。
- 使用
super()可以在重写时调用父类的方法,避免重复编码:
class Cat(Animal):
def speak(self):
base = super().speak() # 调用父类的 speak
return f"{base} - 喵喵"
单继承是项目中使用最广泛的继承方式,结构扁平、易于理解和维护。
多继承
多继承允许一个子类同时从多个父类派生,可以在一个类中组合多个来源的功能。
class Flyable:
def fly(self):
return "飞"
class Swimmable:
def swim(self):
return "游"
class Duck(Flyable, Swimmable):
pass
d = Duck()
print(d.fly()) # 飞
print(d.swim()) # 游
- 多继承非常强大,但也容易导致菱形继承(一个类通过两条不同路径继承同一个基类)或方法冲突。
- 为了避免混乱,必须清楚 Python 是如何决定“到底调用哪个方法”的,答案就是 MRO。
MRO 方法解析顺序
MRO(Method Resolution Order)是 Python 计算出的属性、方法查找顺序。当一个实例调用 obj.method() 时,Python 会按照 MRO 列表从当前类开始,逐级向上查找对应的方法。
C3 线性化算法
Python 使用C3 线性化算法来计算新式类(Python 3 中所有类都是新式类)的 MRO,它保证:
- 子类始终在父类之前。
- 父类的顺序与定义时的顺序一致。
- 对于复杂的菱形继承,仍能生成单调、无歧义的查找顺序。
查看一个类的 MRO 有两种方式:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(D.mro())
# 同样的输出,但返回列表
一个经典的菱形继承示例:
A
/ \
B C
\ /
D
对应代码:
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
print(D().method()) # B,因为 MRO 中 B 在 C 前
print(D.__mro__)
# (D, B, C, A, object)
- 在
D中未定义method,Python 会按顺序找B,找到了就调用B.method()。 - 如果
B和C都没有定义,会最终找到A。
super() 与 MRO 的配合super() 并不是直接调用父类方法,而是按照 MRO 顺序查找下一个类的方法。这在多继承场景下非常重要:
class A:
def method(self):
print("A")
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().method()
# 输出顺序:
# D
# B
# C
# A
因为 D 的 MRO 是 D -> B -> C -> A -> object,super().method() 严格沿着这个顺序执行,这才保证了协作式多继承时每个类的方法都能按顺序被调用。
设计建议
- 能用组合代替继承的场景,优先使用组合(把一个类的对象作为另一个类的属性)。
- 多继承尽量使用 Mixin 类:Mixin 是一个只提供特定功能、不应当单独实例化的类,它自身没有复杂的继承链,只是“混入”功能。
- 显式声明
super().init()并在多继承时保持参数一致性,避免因为参数不匹配导致运行时错误。 - 如果发现 MRO 让你困惑,那块代码很可能需要重构以减少复杂度。
掌握 MRO,你就能自信地设计多继承类结构,写出行为可预测、可维护的 Python 代码。