Skip to content

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、出版年份,还要能借阅、归还、展示编目信息。

不使用继承:

python
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. 如果要增加"借阅状态变更日志",需要改三个类

问题:

  • 大量重复代码
  • 修改公共逻辑需要在多处同步
  • 无法统一管理图书类型

使用继承:

python
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、借阅能力)传给后代。PhysicalBookEBook 是两个分支的后代,它们在继承共同基因的基础上,各自发展出自己的特长(页数 vs 文件大小)。AudioBook 则更有意思 — 它同时从 PhysicalBook 一支和 DigitalMixin 一支继承了能力,就像一个人同时继承了父系和母系的基因。

祖先 BookItem(共同基因)
  ├── 实体血脉 PhysicalBook(+ 页数)
  │     └── 融合 AudioBook(PhysicalBook + DigitalMixin)
  └── 数字血脉 EBook(+ 文件大小)

DigitalMixin(外部基因)
  └── 提供数字内容下载能力

L1 理解层:会用

继承基础

语法

python
# 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 = pages

class PhysicalBook(BookItem) 中的括号表示继承关系。PhysicalBook 自动拥有 BookItem 的所有属性和方法:

python
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)     # 792

super() 的使用

super() 用于在子类中调用父类的方法:

python
# PhysicalBook 的 __init__
def __init__(self, title, author, isbn, year, pages):
    super().__init__(title, author, isbn, year)  # 调用 BookItem.__init__
    self._pages = pages                          # 再添加自有属性
python
# 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() → 追加格式和大小

实际应用:图书管理中的继承

在实际的图书馆系统中,不同类型的图书共享大量公共逻辑,但各自又有独特的属性。用继承来组织最为自然:

python
# 基类:所有图书的共同行为
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 是最典型的例子:

python
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 从两个父类获得能力:

来源获得的能力
PhysicalBookBookItemtitle, author, isbn, year, pages, checkout(), return_item(), get_info()
DigitalMixindownload_info()

MRO(方法解析顺序)

多重继承时,Python 按特定顺序查找方法。查看 AudioBook 的 MRO:

python
>>> 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]                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
python
# 验证 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                                        │
└─────────────────────────────────────────────────────────────┘
python
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__ 只执行一次!             │                    │
│  └─────────────────────────────────────┘                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
python
# 实验:验证菱形继承中 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.pydemo_ch03 函数,看继承层次在实际运行中的效果:

python
# 运行: 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__() 而非直接调用父类支持多重继承的 MROsuper().__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__()

反模式:不要这样做

python
# ❌ 直接调用父类而非 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
python
# ❌ 重写方法时完全忽略父类行为
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}页"
python
# ❌ 不是 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]
    重复直到所有列表为空
python
# 验证:自己实现 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 结果 │
└────────────────────────────────────────────────────┘
python
# 验证 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"时,才用显式类名调用:

python
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.attrO(1) 均摊MRO 已缓存为元组,按顺序查 __dict__
MRO 计算类定义时 O(n)n = 继承链长度,计算一次后缓存
super().__init__() 调用O(m)m = MRO 链长度(每个父类只执行一次)
isinstance() 检查O(1)遍历 MRO 元组,通常很短
深度继承(10 层)每次查找 O(10)理论开销,实际可忽略
python
# 验证: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() 委托给 BookItemget_info()


本章能力清单

完成本章后,你应该能够:

  • [ ] 定义子类并建立继承关系
  • [ ] 使用 super() 调用父类方法
  • [ ] 在子类中重写父类方法并保留父类行为
  • [ ] 实现多重继承并理解 MRO 查找顺序
  • [ ] 用 类名.__mro__ 查看方法解析顺序
  • [ ] 解释 C3 线性化算法的基本原理
  • [ ] 分析菱形继承问题并用 super() 正确解决
  • [ ] 使用 Mixin 模式组合横切关注点
  • [ ] 判断 is-a 关系是否适合用继承表达
  • [ ] 设计不超过 3 层的合理继承层次