03-继承
Python 版本要求:Python 3.11+ 贯穿项目:oop_demo/ 代码位置:
oop_demo/app/domain/book/model.py+oop_demo/app/domain/book/mixins.py测试验证:cd oop_demo && uv run pytest -k inherit -v
概念铺垫
为什么需要继承?
问题场景
图书馆有三种图书:纸质书、电子书、有声书。每本书都需要书名、作者、ISBN、出版年份,还要能借阅、归还、展示编目信息。
不使用继承:
class PhysicalBook:
def __init__(self, title: str, author: str, isbn: str, year: int, pages: int) -> None:
self.title = title
self.author = author
self._isbn = isbn
self._year = year
self._available = True
self._pages = pages
def get_info(self) -> str:
return f"[{self._isbn}] {self.title} — {self.author} ({self._year}) | {self._pages} 页"
def checkout(self) -> bool:
if self._available:
self._available = False
return True
return False
def return_item(self) -> None:
self._available = True
class EBook:
def __init__(self, title: str, author: str, isbn: str, year: int, file_size_mb: float) -> None:
self.title = title
self.author = author
self._isbn = isbn
self._year = year
self._available = True
self.__file_size_mb = file_size_mb
def get_info(self) -> str:
return f"[{self._isbn}] {self.title} — {self.author} ({self._year}) | {self.__file_size_mb} MB"
def checkout(self) -> bool:
if self._available:
self._available = False
return True
return False
def return_item(self) -> None:
self._available = True
# 问题:
# 1. title/author/isbn/year/_available 在三种类中重复定义
# 2. checkout()/return_item() 逻辑完全相同
# 3. get_info() 有公共部分也有差异部分
# 4. 如果要增加"借阅状态变更日志",需要改三个类问题:
- 大量重复代码
- 修改公共逻辑需要在多处同步
- 无法统一管理图书类型
使用继承:
class BookItem:
"""图书基类 — 所有图书共享的属性和行为"""
def __init__(self, title: str, author: str, isbn: str, year: int) -> None:
self.title = title
self.author = author
self._isbn = isbn
self._year = year
self._available = True
def get_info(self) -> str:
return f"[{self._isbn}] {self.title} — {self.author} ({self._year})"
def checkout(self) -> bool:
if self._available:
self._available = False
return True
return False
def return_item(self) -> None:
self._available = True
class PhysicalBook(BookItem):
"""纸质书 — 在 BookItem 基础上增加页数"""
def __init__(self, title, author, isbn, year, pages):
super().__init__(title, author, isbn, year)
self._pages = pages
def get_info(self) -> str:
return f"{super().get_info()} | {self._pages} 页"
class EBook(BookItem):
"""电子书 — 在 BookItem 基础上增加文件大小"""
def __init__(self, title, author, isbn, year, file_size_mb):
super().__init__(title, author, isbn, year)
self.__file_size_mb = file_size_mb
def get_info(self) -> str:
return f"{super().get_info()} | {self.__file_size_mb} MB"
# 公共代码只在 BookItem 中写一次
# 子类只关注自己的特性
# 修改 BookItem.checkout() 自动影响所有子类优势:
- 消除重复代码
- 易于维护和扩展
- 统一管理公共行为
生活类比
家族基因:
把继承想象成家族基因传承。BookItem 是祖先,它把核心基因(书名、作者、ISBN、借阅能力)传给后代。PhysicalBook 和 EBook 是两个分支的后代,它们在继承共同基因的基础上,各自发展出自己的特长(页数 vs 文件大小)。AudioBook 则更有意思 — 它同时从 PhysicalBook 一支和 DigitalMixin 一支继承了能力,就像一个人同时继承了父系和母系的基因。
祖先 BookItem(共同基因)
├── 实体血脉 PhysicalBook(+ 页数)
│ └── 融合 AudioBook(PhysicalBook + DigitalMixin)
└── 数字血脉 EBook(+ 文件大小)
DigitalMixin(外部基因)
└── 提供数字内容下载能力L1 理解层:会用
继承基础
语法
# domain/book/model.py
class BookItem:
"""图书基类"""
def __init__(self, title: str, author: str, isbn: str, year: int) -> None:
self.title: str = title
self.author: str = author
self._isbn: str = isbn
self._year: int = year
self._available: bool = True
class PhysicalBook(BookItem):
"""实体书 — 继承自 BookItem"""
def __init__(self, title: str, author: str, isbn: str, year: int, pages: int) -> None:
super().__init__(title, author, isbn, year)
self._pages: int = pagesclass PhysicalBook(BookItem) 中的括号表示继承关系。PhysicalBook 自动拥有 BookItem 的所有属性和方法:
pb = PhysicalBook("流畅的Python", "Ramalho", "9781491946008", 2015, 792)
# 继承自 BookItem 的属性
print(pb.title) # 流畅的Python
print(pb.author) # Ramalho
print(pb.isbn) # 9781491946008 (通过 @property)
print(pb.available) # True (通过 @property)
# 继承自 BookItem 的方法
print(pb.checkout()) # True
print(pb.return_item()) # None
# PhysicalBook 自有属性
print(pb.pages) # 792super() 的使用
super() 用于在子类中调用父类的方法:
# PhysicalBook 的 __init__
def __init__(self, title, author, isbn, year, pages):
super().__init__(title, author, isbn, year) # 调用 BookItem.__init__
self._pages = pages # 再添加自有属性# PhysicalBook 的 get_info — 重写并扩展
def get_info(self) -> str:
return f"{super().get_info()} | {self._pages} 页"
# ↑ 先获取父类的编目信息
# ↑ 再追加页数信息两种常见用法:
| 场景 | 代码 | 说明 |
|---|---|---|
__init__ 中调用父类初始化 | super().__init__(title, author, isbn, year) | 复用父类初始化逻辑 |
| 重写方法中保留父类行为 | `return f" | 扩展"` |
继承层次
BookItem(基类)
│ 属性:title, author, _isbn, _year, _available
│ 方法:get_info(), checkout(), return_item(), is_available()
│ get_isbn(), __str__(), __repr__(), __eq__(), __hash__()
│
├── PhysicalBook(子类)
│ │ 继承:BookItem 的所有属性和方法
│ │ 自有:_pages
│ │ 重写:get_info() → 追加页数
│ │
│ └── AudioBook(多重继承)
│ │ 继承:PhysicalBook + DigitalMixin
│ │ 自有:_file_size_mb, _format
│ │ 重写:get_info() → 追加有声书信息
│
└── EBook(子类)
│ 继承:BookItem 的所有属性和方法
│ 自有:__file_size_mb, _format
│ 重写:get_info() → 追加格式和大小实际应用:图书管理中的继承
在实际的图书馆系统中,不同类型的图书共享大量公共逻辑,但各自又有独特的属性。用继承来组织最为自然:
# 基类:所有图书的共同行为
class BookItem:
total_books: int = 0
def __init__(self, title: str, author: str, isbn: str, year: int) -> None:
self.title: str = title
self.author: str = author
self._isbn: str = isbn
self._year: int = year
self._available: bool = True
BookItem.total_books += 1
@property
def isbn(self) -> str:
return self._isbn
@property
def available(self) -> bool:
return self._available
@classmethod
def get_total(cls) -> int:
return cls.total_books
@staticmethod
def validate_isbn(isbn: str) -> bool:
digits = isbn.replace("-", "")
return len(digits) in (10, 13) and digits.isdigit()
def get_info(self) -> str:
return f"[{self._isbn}] {self.title} — {self.author} ({self._year})"
def checkout(self) -> bool:
if self._available:
self._available = False
return True
return False
def return_item(self) -> None:
self._available = True
def __str__(self) -> str:
status = "可借" if self._available else "已借出"
return f"《{self.title}》{self.author}({status})"
# 子类1:纸质书
class PhysicalBook(BookItem):
def __init__(self, title, author, isbn, year, pages: int) -> None:
super().__init__(title, author, isbn, year)
self._pages: int = pages
@property
def pages(self) -> int:
return self._pages
def get_info(self) -> str:
return f"{super().get_info()} | {self._pages} 页"
# 子类2:电子书
class EBook(BookItem):
def __init__(self, title, author, isbn, year, file_size_mb: float, fmt: str = "PDF") -> None:
super().__init__(title, author, isbn, year)
self.__file_size_mb: float = file_size_mb
self._format: str = fmt
@property
def file_size_mb(self) -> float:
return self.__file_size_mb
@file_size_mb.setter
def file_size_mb(self, value: float) -> None:
if value <= 0:
raise ValueError("文件大小必须大于 0 MB")
self.__file_size_mb = value
def get_info(self) -> str:
return f"{super().get_info()} | {self._format} {self.__file_size_mb:.1f} MB"
# 子类3:有声书(多重继承)
class AudioBook(PhysicalBook, DigitalMixin):
def __init__(self, title, author, isbn, year, pages: int, file_size_mb: float) -> None:
super().__init__(title, author, isbn, year, pages)
self._file_size_mb = file_size_mb
self._format = "MP3"
def get_info(self) -> str:
return f"{super().get_info()} | 有声书 {self.download_info()}"
# 统一处理所有图书类型
books: list[BookItem] = [
PhysicalBook("流畅的Python", "Ramalho", "9781491946008", 2015, 792),
EBook("深入理解Python", "作者", "9780000000002", 2022, 5.2, "EPUB"),
AudioBook("代码大全", "McConnell", "9780735619678", 2004, 960, 150.0),
]
for book in books:
print(f"{book.__class__.__name__}: {book.get_info()}")
print(f" 可借: {book.is_available()}")关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
super().__init__(title, author, isbn, year) | 调用父类构造函数 | 复用父类初始化逻辑,避免在子类中重复赋值 |
| `return f" | {self._pages} 页"` | 重写并扩展父类方法 |
@property / @setter | 属性访问控制 | 封装内部状态,提供验证逻辑 |
books: list[BookItem] = [...] | 多态统一处理 | 利用多态,无需判断具体类型即可调用 get_info() |
多重继承
AudioBook 的继承链
Python 支持多重继承,一个子类可以同时继承多个父类。在 oop_demo 中,AudioBook 是最典型的例子:
class DigitalMixin:
"""数字内容 Mixin — 提供文件信息展示能力"""
_file_size_mb: float
_format: str
def download_info(self) -> str:
return f"{self._format} | {self._file_size_mb:.1f} MB"
class AudioBook(PhysicalBook, DigitalMixin):
"""有声书 — 多重继承 (PhysicalBook + DigitalMixin)"""
def __init__(self, title, author, isbn, year, pages, file_size_mb):
super().__init__(title, author, isbn, year, pages)
self._file_size_mb = file_size_mb
self._format = "MP3"
def get_info(self) -> str:
return f"{super().get_info()} | 有声书 {self.download_info()}"AudioBook 从两个父类获得能力:
| 来源 | 获得的能力 |
|---|---|
PhysicalBook → BookItem | title, author, isbn, year, pages, checkout(), return_item(), get_info() |
DigitalMixin | download_info() |
MRO(方法解析顺序)
多重继承时,Python 按特定顺序查找方法。查看 AudioBook 的 MRO:
>>> AudioBook.__mro__
(AudioBook, PhysicalBook, BookItem, Catalogable, DigitalMixin, object)
>>> [c.__name__ for c in AudioBook.__mro__]
['AudioBook', 'PhysicalBook', 'BookItem', 'Catalogable', 'DigitalMixin', 'object']MRO 规则:
┌─────────────────────────────────────────────────────────────┐
│ 方法解析顺序(MRO) │
│ │
│ MRO = Method Resolution Order │
│ │
│ 规则: │
│ 1. 从子类到父类依次查找 │
│ 2. 同级父类按继承声明顺序 │
│ 3. 每个类只出现一次(避免重复调用) │
│ 4. 使用 C3 算法计算 │
│ │
│ 查看MRO: │
│ • 类名.__mro__ │
│ • 类名.mro() │
│ │
│ AudioBook 示例: │
│ MRO: AudioBook → PhysicalBook → BookItem → Catalogable │
│ → DigitalMixin → object │
└─────────────────────────────────────────────────────────────┘MRO 算法(C3 线性化)
C3 算法原理:
┌─────────────────────────────────────────────────────────────┐
│ Method Resolution Order (MRO) │
├─────────────────────────────────────────────────────────────┤
│ │
│ C3 线性化算法规则: │
│ │
│ 1. 子类优先于父类 │
│ Child.mro = [Child] + merge(Parents.mro) │
│ │
│ 2. 左边父类优先于右边父类 │
│ class D(B, C) → B 优先于 C │
│ │
│ 3. 每个类只出现一次 │
│ 保证方法只调用一次 │
│ │
│ merge 算法: │
│ ┌─────────────────────────────────────┐ │
│ │ 取每个列表的第一个元素: │ │
│ │ • 如果它不在其他列表的尾部出现, │ │
│ │ 则取出并加入结果 │ │
│ │ • 否则跳过,检查下一个列表 │ │
│ │ • 重复直到所有列表为空 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 示例:class AudioBook(PhysicalBook, DigitalMixin) │
│ │
│ PhysicalBook.mro = [PhysicalBook, BookItem, Catalogable, │
│ object] │
│ DigitalMixin.mro = [DigitalMixin, object] │
│ 父类列表 = [PhysicalBook, DigitalMixin] │
│ │
│ merge 过程: │
│ 1. 取 PhysicalBook → ✅ 不在其他列表尾部 │
│ 2. merge([BookItem, Catalogable, object], │
│ [DigitalMixin, object], │
│ [PhysicalBook, DigitalMixin]) │
│ 3. 取 BookItem → ✅ │
│ 4. 取 Catalogable → ✅ │
│ 5. 取 DigitalMixin → ✅ (此时 PhysicalBook 已移除) │
│ 6. 取 object → ✅ │
│ │
│ 结果:[AudioBook, PhysicalBook, BookItem, Catalogable, │
│ DigitalMixin, object] │
│ │
└─────────────────────────────────────────────────────────────┘# 验证 C3 算法结果
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
# 手动计算
# D.mro = [D] + merge(B.mro, C.mro, [B, C])
# B.mro = [B, A, object]
# C.mro = [C, A, object]
# merge([B,A,object], [C,A,object], [B,C])
# = [B, C, A, object]
# 最终:[D, B, C, A, object]
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# 验证方法查找
class B2(A):
def method(self) -> str:
return "B"
class C2(A):
def method(self) -> str:
return "C"
class D2(B2, C2):
pass
print(D2().method()) # "B" — MRO 中 B2 在 C2 前面菱形继承详解
菱形继承: 多个子类继承同一个父类,形成菱形结构。
菱形继承结构:
┌─────────────────────────────────────────────────────────────┐
│ 菱形继承(Diamond Problem) │
│ │
│ A │
│ / \ │
│ B C │
│ \ / │
│ D │
│ │
│ 问题:D 继承 B 和 C,B 和 C 都继承 A │
│ 调用 A 的方法时,会执行几次? │
│ │
│ ⚠️ Python 解决方案:MRO 保证每个类只调用一次 │
│ │
│ D 的 MRO: D → B → C → A → object │
│ │
│ 执行顺序: │
│ 1. D.method() → 调用 super() │
│ 2. B.method() → 调用 super() │
│ 3. C.method() → 调用 super() │
│ 4. A.method() → 返回(只执行一次!) │
│ │
│ 结果链:A → C → B → D │
└─────────────────────────────────────────────────────────────┘class A:
def method(self) -> str:
return "A"
class B(A):
def method(self) -> str:
return super().method() + " → B"
class C(A):
def method(self) -> str:
return super().method() + " → C"
class D(B, C):
def method(self) -> str:
return super().method() + " → D"
d = D()
print(d.method())
# A → C → B → D(每个类只执行一次)菱形继承问题:
┌─────────────────────────────────────────────────────────────┐
│ Diamond Problem │
├─────────────────────────────────────────────────────────────┤
│ │
│ A │
│ / \ │
│ B C │
│ \ / │
│ D │
│ │
│ 问题:D 继承 B 和 C,B 和 C 都继承 A │
│ 调用 D.__init__ 时,A.__init__ 会执行几次? │
│ │
│ ⚠️ 不用 super() 的结果: │
│ ┌─────────────────────────────────────┐ │
│ │ class B(A): │ │
│ │ def __init__(self): │ │
│ │ A.__init__(self) # 直接调用│ │
│ │ │ │
│ │ class C(A): │ │
│ │ def __init__(self): │ │
│ │ A.__init__(self) # 直接调用│ │
│ │ │ │
│ │ class D(B, C): │ │
│ │ def __init__(self): │ │
│ │ B.__init__(self) │ │
│ │ C.__init__(self) │ │
│ │ # A.__init__ 被调用两次! │ │
│ └─────────────────────────────────────┘ │
│ │
│ ✅ 使用 super() 的结果: │
│ ┌─────────────────────────────────────┐ │
│ │ D.__mro__ = [D, B, C, A, object] │ │
│ │ │ │
│ │ D.__init__ → super().__init__ │ │
│ │ → B.__init__ → super().__init__ │ │
│ │ → C.__init__ → super().__init__ │ │
│ │ → A.__init__ → super().__init__ │ │
│ │ → object.__init__ │ │
│ │ │ │
│ │ A.__init__ 只执行一次! │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘# 实验:验证菱形继承中 super() 的行为
class A:
def __init__(self) -> None:
print("A.__init__")
super().__init__() # 重要:继续调用 MRO 中的下一个
class B(A):
def __init__(self) -> None:
print("B.__init__")
super().__init__()
class C(A):
def __init__(self) -> None:
print("C.__init__")
super().__init__()
class D(B, C):
def __init__(self) -> None:
print("D.__init__")
super().__init__()
d = D()
# 输出:
# D.__init__
# B.__init__
# C.__init__
# A.__init__
# (每个 __init__ 只执行一次)
# 对比:如果 A 不调用 super().__init__()
class A2:
def __init__(self) -> None:
print("A2.__init__")
# 没有 super().__init__()
class B2(A2):
def __init__(self) -> None:
print("B2.__init__")
super().__init__()
class C2(A2):
def __init__(self) -> None:
print("C2.__init__")
super().__init__()
class D2(B2, C2):
def __init__(self) -> None:
print("D2.__init__")
super().__init__()
d2 = D2()
# 输出:
# D2.__init__
# B2.__init__
# A2.__init__
# C2.__init__ 不会被调用!因为 A2 没有继续 super()
# 结论:菱形继承中每个类都应该调用 super().__init__()关键代码说明:
| 代码 | 含义 | 为什么这样写 |
|---|---|---|
class D(B, C) + 各类均调用 super().__init__() | 菱形继承的正确写法 | MRO 为 [D, B, C, A, object],super() 自动链式推进,A.__init__ 只执行一次 |
super().__init__() 在 A 中也调用 | 链式终结传递 | 若 A 不调用 super(),C.__init__ 将被截断,整条 MRO 链断裂 |
class A2 中省略 super().__init__() | 演示错误做法 | 导致 C2.__init__ 永远不被调用,菱形继承中某个父类初始化被静默跳过 |
D.__mro__ | 查看方法解析顺序 | MRO 决定了 super() 的委托路径,调试多重继承问题时必须查看 |
贯穿实战
参考 oop_demo/app/main.py 的 demo_ch03 函数,看继承层次在实际运行中的效果:
# 运行: cd oop_demo && uv run python -m app
# demo_ch03 输出:
# ==================================================
# 第 3 章:继承
# ==================================================
# 📌 继承层次:
# BookItem(基类)
# ├── PhysicalBook(继承: title, author, isbn, year + pages)
# │ └── AudioBook(多重继承: PhysicalBook + DigitalMixin)
# └── EBook(继承: title, author, isbn, year + file_size_mb)
# PhysicalBook: 流畅的Python
# 继承自 BookItem: title='流畅的Python', author='Ramalho'
# 自有属性: pages=792
# 重写方法: [9781491946008] 流畅的Python — Ramalho (2015) | 792 页
# EBook: 深入理解Python
# 继承自 BookItem: title='深入理解Python'
# 自有属性: file_size_mb=5.2, format=EPUB
# AudioBook: 代码大全(多重继承 PhysicalBook + DigitalMixin)
# 来自 PhysicalBook: pages=960
# 来自 DigitalMixin: MP3 | 150.0 MB
# 📌 MRO(方法解析顺序):
# AudioBook.__mro__ = ['AudioBook', 'PhysicalBook', 'BookItem', 'Catalogable', 'DigitalMixin', 'object']L2 实践层:用好
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
| 继承层次不超过 3 层 | 避免耦合过高,易于维护 | BookItem → PhysicalBook → AudioBook |
用 super().__init__() 而非直接调用父类 | 支持多重继承的 MRO | super().__init__(title, author, isbn, year) |
| 子类重写方法时保留父类行为 | 避免意外破坏继承链 | `return f" |
| 使用 Mixin 而非深度多重继承 | 避免菱形继承问题 | class AudioBook(PhysicalBook, DigitalMixin) |
| is-a 关系才用继承 | 确保继承语义正确 | PhysicalBook is-a BookItem |
| Mixin 命名加后缀 | 明确职责,易于识别 | DigitalMixin, LoggingMixin |
始终用 super() 而非直接调用父类 | 遵循 MRO,菱形继承中避免重复初始化 | super().__init__(name) |
MRO 链中每个类都调用 super() | 防止链断裂 | 菱形继承中 A 也必须 super().__init__() |
反模式:不要这样做
# ❌ 直接调用父类而非 super() — 菱形继承中会重复初始化
class PhysicalBook(BookItem):
def __init__(self, title, author, isbn, year, pages):
BookItem.__init__(self, title, author, isbn, year) # 绕过 MRO
self.pages = pages
# ✅ 使用 super() — 遵循 MRO
class PhysicalBook(BookItem):
def __init__(self, title, author, isbn, year, pages):
super().__init__(title, author, isbn, year)
self.pages = pages# ❌ 重写方法时完全忽略父类行为
class PhysicalBook(BookItem):
def get_info(self):
return f"{self.title} — {self.pages}页" # 丢失了 BookItem 的 ISBN/作者
# ✅ 保留父类行为并扩展
class PhysicalBook(BookItem):
def get_info(self):
base = super().get_info() # "[978...] 流畅的Python — Ramalho (2015)"
return f"{base}, {self.pages}页"# ❌ 不是 is-a 却用继承
class Library(BookItem): # Library 不是 BookItem!
pass
# ✅ has-a 关系用组合
class Library:
def __init__(self):
self._books: dict[str, BookItem] = {}适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 明确的 is-a 关系 | 推荐 | 语义自然,代码复用合理 |
| 代码复用但无 is-a 关系 | 不推荐 | 用组合替代 |
| 多重继承(菱形结构) | 谨慎 | 需要用 super() + 理解 MRO |
| Mixin 横向能力组合 | 推荐 | 比深度多重继承更安全 |
| 继承层次超过 3 层 | 不推荐 | 耦合过深,重构困难 |
常见陷阱
| 陷阱 | 问题 | 解决方案 |
|---|---|---|
直接调用父类 __init__ | 多重继承时父类被重复调用 | 使用 super().__init__() |
| 继承层次过深 | 耦合过高,难以维护 | 限制在 3 层以内 |
| 不是 is-a 却继承 | 语义错误,破坏设计 | 使用组合 |
菱形继承中漏调 super() | MRO 链断裂,部分父类不被初始化 | 每个类都调用 super().__init__() |
| 重写方法忽略父类 | 破坏继承链行为 | 先调用 super().method() |
super() 参数缺失 | TypeError | 检查父类方法签名 |
L3 专家层:深入
Python 如何实现:C3 线性化与 super() 内部机制
C3 线性化算法(CPython 源码实现位于 Objects/typeobject.c)
C3 算法的核心是 merge(list_of_lists) 操作:
merge(L1, L2, ..., Ln):
取 L1[0] 作为候选
如果候选不在任何其他列表的尾部:
取出候选,放入结果
否则:
跳过 L1,检查 L2[0]
重复直到所有列表为空# 验证:自己实现 C3 算法
def merge(*lists):
result = []
while True:
# 去除空列表
lists = [l for l in lists if l]
if not lists:
return result
# 尝试每个列表的头部
for lst in lists:
head = lst[0]
# 检查 head 不在其他列表的尾部
if not any(head in other[1:] for other in lists):
result.append(head)
# 从所有列表中移除 head
for l in lists:
if l and l[0] == head:
l.pop(0)
break
else:
raise TypeError("无法创建一致的 MRO")
# 验证 D(B, C) 继承自 A 的 MRO
print(merge(
['D'],
['B', 'A', 'object'],
['C', 'A', 'object'],
['B', 'C']
))
# ['D', 'B', 'C', 'A', 'object']super() 的内部机制
super() 在 Python 3 中是一个编译器内置操作。当你写 super().method() 时:
super().method()
│
▼
┌────────────────────────────────────────────────────┐
│ super() 构造时,CPython 从调用帧中提取: │
│ - __class__ (当前类,编译时注入到本地变量) │
│ - self (第一个参数) │
│ │
│ 等价于: super(__class__, self) │
│ │
│ super().method() 查找过程: │
│ 1. 获取 __class__ 的 MRO 列表 │
│ 2. 找到 __class__ 在 MRO 中的位置 │
│ 3. 从 __class__ 的下一个类开始查找 method │
│ 4. 绑定 self,返回 bound method 或 descriptor 结果 │
└────────────────────────────────────────────────────┘# 验证 super() 的查找机制
class A:
def who(self):
return "A"
class B(A):
def who(self):
return f"B({super().who()})"
class C(A):
def who(self):
return f"C({super().who()})"
class D(B, C):
def who(self):
return f"D({super().who()})"
# super() 每次都调用 MRO 中的下一个
print(D.__mro__) # (D, B, C, A, object)
print(D().who()) # D(B(C(A)))
# 关键:super() 不是"调用父类",而是"调用 MRO 中的下一个"
# 在 D 中 super() 下一个是 B,在 B 中 super() 下一个是 C(不是 A!)何时不该用 super():
当你需要"直接调用某个特定父类"而非"遵循 MRO"时,才用显式类名调用:
class A:
def __init__(self):
self.a = 1
class B:
def __init__(self):
self.b = 2
class C(A, B):
def __init__(self):
A.__init__(self) # 只想初始化 A
B.__init__(self) # 只想初始化 B
# 不用 super() 因为 A 和 B 的 __init__ 不兼容(参数不同)性能考量
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
属性/方法查找 obj.attr | O(1) 均摊 | MRO 已缓存为元组,按顺序查 __dict__ |
| MRO 计算 | 类定义时 O(n) | n = 继承链长度,计算一次后缓存 |
super().__init__() 调用 | O(m) | m = MRO 链长度(每个父类只执行一次) |
isinstance() 检查 | O(1) | 遍历 MRO 元组,通常很短 |
| 深度继承(10 层) | 每次查找 O(10) | 理论开销,实际可忽略 |
# 验证:MRO 是缓存的元组,查找高效
import timeit
class A: def method(self): pass
class B(A): pass
class C(B): pass
class D(C): pass
class E(D): pass
# MRO 只在类创建时计算一次
print(E.__mro__) # (<class 'D'>, <class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
# 方法调用开销极小(均摊 O(1))
e = E()
print(timeit.timeit("e.method()", globals={'e': e}, number=1000000))
# ~0.1 秒(含方法调用本身)知识关联
继承知识关联:
┌───────────────┐
│ 第 1 章:类 │
│ 类定义/实例化 │
└───────┬───────┘
│
↓
┌───────────────┐
│ 第 3 章:继承 │
│ super()/MRO │
└───────┬───────┘
│
┌────────────┼────────────┐
↓ ↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 多继承 │ │ Mixin │ │ 多态(第5章)│
│ MRO/C3 │ │ 组合能力 │ │ ABC/Proto │
└───────────┘ └───────────┘ └───────────┘
│ │ │
↓ ↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 菱形继承 │ │ 设计原则 │ │ 设计原则 │
│ 问题解决 │ │ SOLID │ │ LSP/OCP │
└───────────┘ └───────────┘ └───────────┘
│
↓
┌───────────┐
│ C3 线性化 │ ← typeobject.c 中的 mro_implementation()
│ 算法 │ merge(lists) 核心逻辑
│ type.__mro__│
└───────────┘自检清单
1. super() 和直接调用父类有什么区别?
super() 按照 MRO 顺序委托方法调用,在多重继承下能保证每个类只被调用一次。直接调用父类(如 BookItem.__init__(self))会绕过 MRO,在菱形继承中导致父类被重复初始化。
2. AudioBook 的 MRO 是什么?为什么 DigitalMixin 排在 BookItem 后面?
MRO 是 [AudioBook, PhysicalBook, BookItem, Catalogable, DigitalMixin, object]。因为 AudioBook(PhysicalBook, DigitalMixin) 声明中 PhysicalBook 在左边,按 C3 算法,PhysicalBook 及其祖先(BookItem, Catalogable)都排在 DigitalMixin 前面。
3. 菱形继承中如果最顶层的类不调用 super(),会发生什么?
MRO 链会在该类处断裂,排在它后面的类不会被初始化。例如 A 不调用 super().__init__(),则 C.__init__ 永远不会被执行。
4. 什么时候该用继承,什么时候该用组合?
is-a 关系用继承(PhysicalBook is-a BookItem),has-a 关系用组合(Library has-a list[BookItem])。如果只是为了复用代码但没有 is-a 关系,优先考虑组合。
5. PhysicalBook.get_info() 中 super().get_info() 调用的是哪个类的方法?
调用的是 BookItem.get_info()。PhysicalBook 的下一个 MRO 节点是 BookItem,所以 super() 委托给 BookItem 的 get_info()。
本章能力清单
完成本章后,你应该能够:
- [ ] 定义子类并建立继承关系
- [ ] 使用
super()调用父类方法 - [ ] 在子类中重写父类方法并保留父类行为
- [ ] 实现多重继承并理解 MRO 查找顺序
- [ ] 用
类名.__mro__查看方法解析顺序 - [ ] 解释 C3 线性化算法的基本原理
- [ ] 分析菱形继承问题并用
super()正确解决 - [ ] 使用 Mixin 模式组合横切关注点
- [ ] 判断 is-a 关系是否适合用继承表达
- [ ] 设计不超过 3 层的合理继承层次