1. 引言:为什么继承值得认真对待
继承是面向对象编程中最容易被滥用、也最容易被误解的机制之一。Python 作为一门支持多继承的语言,给了我们比 Java、C# 更灵活的组合能力,但同时也带来了「菱形继承」「方法解析顺序(MRO)」「super() 到底调谁」等一系列需要真正理解的问题。
本文从单继承讲起,逐步深入到多继承的 MRO 算法、super() 的真实语义、协作式继承(Cooperative Inheritance)的写法,最后给出工程实践中的取舍建议。读完之后,你应该能回答下面几个问题:
- 单继承下
super()的行为为什么是「可预测」的? - 多继承时 Python 用什么算法决定方法查找顺序?
- 为什么
super().__init__()有时会「跳过」某个父类? - 什么时候该用多继承,什么时候该用组合或 Mixin?
2. 单继承:最基础也最可靠的模型
2.1 基本语法与对象模型
单继承指一个类只有一个直接父类。语法上非常直观:
python
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
return f"{self.name} makes a sound"
class Dog(Animal):
def __init__(self, name: str, breed: str):
super().__init__(name) # 调用父类构造
self.breed = breed
def speak(self) -> str:
return f"{self.name} barks"
dog = Dog("旺财", "柴犬")
print(dog.speak()) # 旺财 barks
print(isinstance(dog, Animal)) # True
在单继承下,属性查找是一条线性链 :Dog -> Animal -> object。Python 会沿着这条链依次查找,找到即返回。这条链就是单继承的 MRO(Method Resolution Order),只不过它退化成了一条直线,没有任何歧义。
2.2 单继承下的 super() 语义
很多人把 super() 理解成「调用父类方法」,这在单继承下恰好成立,但语义并不准确。super() 的真实含义是:
在当前实例所属类的 MRO 列表中,从当前类之后的位置继续查找。
单继承时 MRO 只有一条直线,所以「当前类之后」自然就是父类,行为完全可预测:
python
class A:
def hello(self):
print("A.hello")
class B(A):
def hello(self):
print("B.hello")
super().hello() # MRO: B -> A -> object,从 B 之后找,命中 A
class C(B):
def hello(self):
print("C.hello")
super().hello() # MRO: C -> B -> A -> object,从 C 之后找,命中 B
C().hello()
# C.hello
# B.hello
# A.hello
可以看到,super() 沿着 MRO 逐级向上,形成一条清晰的调用链。这是单继承最舒服的地方:调用顺序与代码书写顺序一致,心智负担极低。
2.3 单继承的典型适用场景
- is-a 关系明确 :
Dog是一种Animal,SavingsAccount是一种Account。 - 模板方法模式:父类定义流程骨架,子类只覆写钩子方法。
- 框架扩展点 :如 Django 的
Model、View,通过继承获得大量默认行为。
单继承的缺点也很明显:一个类只能沿一条主线演化,当需要「同时具备多种能力」时,继承链会变得又深又长,最终形成难以维护的「继承地狱」。
3. 多继承:能力更强,规则更复杂
3.1 基本语法
多继承允许一个类同时继承多个父类:
python
class Flyable:
def fly(self):
return "I can fly"
class Swimmable:
def swim(self):
return "I can swim"
class Duck(Flyable, Swimmable):
pass
duck = Duck()
print(duck.fly()) # I can fly
print(duck.swim()) # I can swim
Duck 同时获得了 fly 和 swim 两个能力。看起来很美,但问题随之而来:如果两个父类有同名方法,Python 该选哪个?
3.2 菱形继承问题
多继承最经典的坑是菱形继承(Diamond Problem):
python
class A:
def hello(self):
print("A.hello")
class B(A):
def hello(self):
print("B.hello")
class C(A):
def hello(self):
print("C.hello")
class D(B, C):
pass
D().hello() # 输出什么?
D 同时继承 B 和 C,而 B、C 又都继承自 A。如果按「深度优先、从左到右」的朴素规则,D -> B -> A -> C,会先命中 A.hello,这显然不合理------C 的覆写被完全忽略了。
C++ 用虚继承解决这个问题,而 Python 选择了另一条路:C3 线性化算法。
3.3 MRO 与 C3 线性化
Python 2.3 之后,所有新式类都使用 C3 线性化算法计算 MRO。它的核心思想是:保证每个类在其所有父类之前出现,且保持基类声明顺序。
上面的例子中,D 的 MRO 是:
python
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
所以 D().hello() 输出 B.hello------B 在 MRO 中排在 C 前面,符合「从左到右」的声明顺序,同时 A 被排到了最后,避免了重复调用。
C3 算法的合并规则可以简化为:
- 取第一个父类的 MRO 列表;
- 依次检查每个列表的头部,如果该头部没有出现在其他任何列表的尾部,就把它取出加入结果;
- 重复直到所有列表为空。
如果无法完成合并(出现循环依赖),Python 会直接抛出 TypeError: Cannot create a consistent method resolution order。
python
class X: pass
class Y: pass
class Z(X, Y): pass
# class W(Y, X, Z): pass # TypeError: MRO conflict
3.4 用 mermaid 直观理解 MRO
#mermaid-svg-hoxyD5fVV6ge0NlI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hoxyD5fVV6ge0NlI .error-icon{fill:#552222;}#mermaid-svg-hoxyD5fVV6ge0NlI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hoxyD5fVV6ge0NlI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hoxyD5fVV6ge0NlI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hoxyD5fVV6ge0NlI .marker.cross{stroke:#333333;}#mermaid-svg-hoxyD5fVV6ge0NlI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hoxyD5fVV6ge0NlI p{margin:0;}#mermaid-svg-hoxyD5fVV6ge0NlI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI .cluster-label text{fill:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI .cluster-label span{color:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI .cluster-label span p{background-color:transparent;}#mermaid-svg-hoxyD5fVV6ge0NlI .label text,#mermaid-svg-hoxyD5fVV6ge0NlI span{fill:#333;color:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI .node rect,#mermaid-svg-hoxyD5fVV6ge0NlI .node circle,#mermaid-svg-hoxyD5fVV6ge0NlI .node ellipse,#mermaid-svg-hoxyD5fVV6ge0NlI .node polygon,#mermaid-svg-hoxyD5fVV6ge0NlI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hoxyD5fVV6ge0NlI .rough-node .label text,#mermaid-svg-hoxyD5fVV6ge0NlI .node .label text,#mermaid-svg-hoxyD5fVV6ge0NlI .image-shape .label,#mermaid-svg-hoxyD5fVV6ge0NlI .icon-shape .label{text-anchor:middle;}#mermaid-svg-hoxyD5fVV6ge0NlI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hoxyD5fVV6ge0NlI .rough-node .label,#mermaid-svg-hoxyD5fVV6ge0NlI .node .label,#mermaid-svg-hoxyD5fVV6ge0NlI .image-shape .label,#mermaid-svg-hoxyD5fVV6ge0NlI .icon-shape .label{text-align:center;}#mermaid-svg-hoxyD5fVV6ge0NlI .node.clickable{cursor:pointer;}#mermaid-svg-hoxyD5fVV6ge0NlI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hoxyD5fVV6ge0NlI .arrowheadPath{fill:#333333;}#mermaid-svg-hoxyD5fVV6ge0NlI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hoxyD5fVV6ge0NlI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hoxyD5fVV6ge0NlI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hoxyD5fVV6ge0NlI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hoxyD5fVV6ge0NlI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hoxyD5fVV6ge0NlI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hoxyD5fVV6ge0NlI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hoxyD5fVV6ge0NlI .cluster text{fill:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI .cluster span{color:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hoxyD5fVV6ge0NlI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hoxyD5fVV6ge0NlI rect.text{fill:none;stroke-width:0;}#mermaid-svg-hoxyD5fVV6ge0NlI .icon-shape,#mermaid-svg-hoxyD5fVV6ge0NlI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hoxyD5fVV6ge0NlI .icon-shape p,#mermaid-svg-hoxyD5fVV6ge0NlI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hoxyD5fVV6ge0NlI .icon-shape .label rect,#mermaid-svg-hoxyD5fVV6ge0NlI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hoxyD5fVV6ge0NlI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hoxyD5fVV6ge0NlI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hoxyD5fVV6ge0NlI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} D(B, C)
B(A)
C(A)
A
object
MRO 计算结果的线性顺序为:D -> B -> C -> A -> object。注意 A 只出现一次,且位于 B、C 之后。
4. super() 在多继承中的真实行为
4.1 super() 不是「父类」,而是「MRO 中的下一个」
这是理解多继承的关键。super() 返回的是一个代理对象,它绑定到实例所属类的 MRO,并从「当前类」之后的位置开始查找。
python
class A:
def hello(self):
print("A.hello")
class B(A):
def hello(self):
print("B.hello")
super().hello()
class C(A):
def hello(self):
print("C.hello")
super().hello()
class D(B, C):
def hello(self):
print("D.hello")
super().hello()
D().hello()
# D.hello
# B.hello
# C.hello
# A.hello
输出顺序完全由 MRO D -> B -> C -> A -> object 决定。注意 B.hello 中的 super() 并没有调用 A.hello,而是调用了 C.hello------因为 B 在 MRO 中的下一个是 C,不是 A。
这就是很多人踩坑的地方:在单继承里 super() 等价于父类,在多继承里它等价于 MRO 的下一个。
4.2 协作式继承:让 super() 链条完整
要让上面的链条正常工作,每个类都必须「配合」调用 super()。如果某个类中途不调用,链条就会断掉:
python
class B(A):
def hello(self):
print("B.hello")
# 忘记调用 super().hello()
class C(A):
def hello(self):
print("C.hello")
super().hello()
class D(B, C):
def hello(self):
print("D.hello")
super().hello()
D().hello()
# D.hello
# B.hello <- C 和 A 都被跳过了
这种「每个类都调用 super()」的写法称为协作式继承(Cooperative Inheritance)。它是多继承能够安全工作的前提。
4.3 构造函数的协作式写法
构造函数是最容易出问题的地方。正确的写法是让每个类都接受 **kwargs 并透传:
python
class Base:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print("Base.__init__")
class LogMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print("LogMixin.__init__")
class CacheMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print("CacheMixin.__init__")
class Service(LogMixin, CacheMixin, Base):
def __init__(self, name: str, **kwargs):
super().__init__(**kwargs)
self.name = name
print(f"Service.__init__: {name}")
Service(name="order")
# Base.__init__
# CacheMixin.__init__
# LogMixin.__init__
# Service.__init__: order
注意输出顺序是逆序 的:因为每个 super().__init__() 都在打印之前调用,所以最底层的 Base 最先打印。这种「洋葱式」调用链正是协作式继承的典型特征。
5. 单继承 vs 多继承:对比与取舍
5.1 核心差异对比
| 维度 | 单继承 | 多继承 |
|---|---|---|
| MRO 复杂度 | 线性,无歧义 | C3 线性化,需理解算法 |
| super() 语义 | 等价于父类 | 等价于 MRO 下一个 |
| 菱形问题 | 不存在 | 存在,需靠 MRO 解决 |
| 代码可读性 | 高 | 中低,依赖约定 |
| 能力复用 | 单一主线 | 多能力组合 |
| 典型风险 | 继承链过深 | 方法冲突、初始化顺序错乱 |
5.2 什么时候用单继承
- 关系是明确的 is-a;
- 只需要沿一条主线扩展;
- 希望代码对团队新人友好,降低理解成本。
5.3 什么时候用多继承
多继承真正有价值的场景是 Mixin(混入):一个只提供单一能力、不表示 is-a 关系、通常不单独实例化的小类。
python
class JsonSerializableMixin:
def to_json(self) -> str:
import json
return json.dumps(self.__dict__, ensure_ascii=False)
class TimestampMixin:
def created_at(self) -> str:
from datetime import datetime
return datetime.now().isoformat()
class Order(JsonSerializableMixin, TimestampMixin):
def __init__(self, order_id: str, amount: float):
self.order_id = order_id
self.amount = amount
order = Order("A001", 99.9)
print(order.to_json()) # {"order_id": "A001", "amount": 99.9}
print(order.created_at()) # 2026-10-28T17:48:53.xxxxxx
Mixin 的命名约定是 XxxMixin,且应该放在继承列表的最左边(因为 MRO 从左到右,Mixin 的方法优先级更高,可以覆写主类行为)。
5.4 优先使用组合
《Effective Python》和《流畅的 Python》都反复强调:优先使用组合而非继承。当你不确定是 is-a 还是 has-a 时,几乎总是 has-a。
python
# 不推荐:用继承表达 has-a
class OrderWithLogger(Logger):
...
# 推荐:用组合
class Order:
def __init__(self, logger: Logger):
self.logger = logger
组合的优势:依赖关系显式、易于测试(可注入 mock)、不会引入 MRO 复杂度、运行时可以替换实现。
6. 常见陷阱与最佳实践
6.1 陷阱一:忘记调用 super()
如前所述,协作式继承链条一旦断裂,后续所有类的方法都不会执行。建议在覆写 __init__ 时总是 调用 super().__init__()。
6.2 陷阱二:Mixin 位置放错
python
class Service(Base, LogMixin): # 错误:Mixin 放右边
pass
由于 MRO 从左到右,Base 的方法会优先于 LogMixin,导致 Mixin 的覆写失效。正确写法是 class Service(LogMixin, Base)。
6.3 陷阱三:在 Mixin 中保存状态
Mixin 应该是无状态 的,或者只依赖 __init__ 中通过 **kwargs 透传的参数。如果 Mixin 自己维护实例状态,多个 Mixin 之间很容易冲突。
6.4 陷阱四:滥用 isinstance 判断
多继承下 isinstance 会返回 True 给所有父类,容易写出意料之外的分支。更推荐用 hasattr 或显式的接口协议(typing.Protocol)。
6.5 最佳实践清单
- 单继承优先,多继承只用于 Mixin;
- Mixin 命名以
Mixin结尾,放在继承列表最左侧; - 所有
__init__都接受**kwargs并透传; - 用
ClassName.__mro__或ClassName.mro()检查 MRO; - 用
super()而非硬编码父类名,保证协作式继承; - 能用组合就不用继承。
7. 总结
单继承和多继承并不是「简单版」和「高级版」的关系,而是两种不同适用场景的工具:
- 单继承提供线性、可预测的 MRO,是绝大多数业务代码的首选;
- 多继承 通过 C3 线性化解决了菱形问题,但要求开发者理解 MRO 与
super()的真实语义,并遵循协作式继承的约定; - Mixin 是多继承最健康的用法,用于横向复用能力而非表达 is-a;
- 组合 在大多数「has-a」场景下优于继承,应作为默认选择。
理解 MRO 不是为了炫技,而是为了在阅读框架源码(如 Django、SQLAlchemy)时不被 super() 的跳转搞晕,也为了在自己设计类层次时,写出别人能看懂、能维护的代码。