📄 一句话概括
重复 5 行降到 0 行、改一处从 2 处降到 1 处——继承不是复制代码,是共享一份代码。
📝 本节和前面几节的关系
4.8 讲同一句调用的公用化,这一节讲同一段代码的公用化。
一、最后一类重复
两个类里有一模一样的 __init__ 和 hello——这是抄,不是复用。
抄一遍就要改两处,回到 3.6 那个坑:漏一处就是一个隐藏的 bug。
更麻烦的是两份会慢慢长得不一样,因为改的人和时间都不同。
二、提到父类
Base 写一遍,A2(Base)、B2(Base) 各写一句 pass。
实测:重复的行从 5 行降到 0 行;改 hello 要动的地方从 2 处降到 1 处。
注意 pass 是很说明问题的:子类一个字没写,却什么都有。
三、B2 一个字没写为什么能用 hello
因为方法是顺着 MRO 往上找的(2.4 讲过)。
B2 自己没有 hello,就往上找到 Base 的——B2 的实例照样能调。
所以继承不是复制代码,是【共享一份代码】。
两者的差别在改的时候:复制要改 N 处,共享只改 1 处。
四、继承的边界
继承解决的是重复定义,不解决【长得像】。
两个类碰巧都有 name 字段,不代表它们该有共同父类。
判断标准:是不是【是一种】的关系?子类能不能替换父类而不出怪事?
用错了比不用更糟:父类一改,整个家族跟着变(2.4 讲的正是这件事)。
五、动手 Demo:三大要素之三——去除类的重复定义的继承
两个类各写一遍相同的初始化和方法,再改用继承写一遍。 数一数重复的行数,以及改一次要动几处。
# 不用继承:两个类各写一遍
class A1:
def __init__(self, name): self.name = name
def hello(self): return "我是 " + self.name
class B1:
def __init__(self, name): self.name = name
def hello(self): return "我是 " + self.name
# 用继承:只写一遍
class Base:
def __init__(self, name): self.name = name
def hello(self): return "我是 " + self.name
class A2(Base): pass
class B2(Base): pass
实跑输出:
--------------------------------------------------------------------
三大要素之三——去除类的重复定义的继承
--------------------------------------------------------------------
两个类里有一模一样的代码——**这是最后一类重复,继承专治它。**
不用继承:A1 和 B1 各写一遍 __init__ 和 hello
用继承: Base 写一遍,A2/B2 各写一句 pass
不用继承 用继承
----------------------------------------------
类里重复的行 5 行 0 行
改 hello 要动 2 处 1 处
B 类还有 hello 吗 自己有一份 继承来的
实测两个版本行为完全一致:**我是 甲/我是 乙**
**注意最后一行**:B2 类里一个字都没写,但 `hello` 照样能用——
**因为方法是顺着 MRO 往上找的(2.4 讲过)。**
**所以继承不是复制代码,是【共享一份代码】。**
实测:重复的行从 5 行降到 0 行,改 hello 要动的地方从 2 处降到 1 处,两个版本行为完全一致。 B2 一个字没写也能用 hello——方法是顺着 MRO 往上找的,所以继承是共享一份代码,不是复制。
30 秒实验
- 找两个有相同方法的类,看它们是不是【是一种】的关系,再决定该不该抽父类。
- 改一次父类里的方法,看有多少子类受影响——这就是继承的代价面。
常见误区
- 以为继承是为了少写代码。它是为了只留一个能改的地方。
- 以为长得像就该继承。碰巧相同不等于是一种。
自测三问(点开对答案)
1. 继承治的是哪一类重复? —— 多个类里有一模一样的代码。实测重复行从 5 行降到 0 行,改动点从 2 处降到 1 处。 2. B2 什么都没写,为什么能用 hello? —— 方法是顺着 MRO 往上找的:B2 自己没有,就找到 Base 的,所以共享一份代码。 3. 继承的判断标准是什么? —— 是不是【是一种】的关系、子类能不能替换父类;碰巧相同不等于该继承,用错了父类一改全家受影响。- ☐ 下一节(4.10 对三大要素的总结)
相关笔记
- 继承对共同点和不同点进行系统的分类和整理 — MRO 与继承的分类
- 三大要素之二:实现调用端公用化的多态 — 上一个要素
- 提高子程序的独立性,强化可维护性 — 只留一个能改的地方
- 🧩 面向对象是怎样工作的 · 索引