继承的直觉 —— 够用即可,不深挖
普通课约 50 分钟
本课导读
会员系统里,VIP 会员和普通会员 90% 一样,就多两个特权——难道把 Member 类整个抄一遍改两行?继承(inheritance)就是治这个的:新模具站在旧模具的肩膀上,只写不同的部分。本课目标是"直觉够用":看懂 class B(A)、会覆盖方法、认识 super()——以及知道什么时候不该用继承。这些直觉将来能让你看懂 PyTorch 里满屏的 class MyModel(nn.Module)。预计 50 分钟。
先感受痛:复制粘贴造新类
书店老板娘要给会员分级。已有普通会员类:
class Member:
def __init__(self, name, points=0):
self.name = name
self.points = points
def add_points(self, n):
self.points = self.points + n
def discount(self):
return 0.95 # 普通会员 95 折
现在要加"金卡会员":其他全一样,只是折扣 88 折、外加一个专属方法。整类抄一遍?两个类从此各自漂移——改个 bug 要改两处,必忘一处。
继承:只写不同的部分
拆解四个动作:
- ①
class 子类(父类):括号一填,父类的属性和方法全部自动继承——GoldMember生下来就会__init__和add_points - ② 覆盖(override):子类写一个同名方法,就用自己的版本替换父类的——金卡的
discount说了算 - ③ 新增:子类随便加自己的新方法,父类和兄弟类都不受影响
- ④⑤ 没写的部分:一律沿用父类——这正是"只写不同的部分"
提示
类比记忆:继承是车型的衍生。基础款(父类)定义了发动机和四个轮子;运动款(子类)是一种基础款——换了悬挂(覆盖),加了尾翼(新增),没动的部分照单全收。判断能不能继承就问一句:"X 是一种 Y 吗?"——金卡会员是一种会员 ✅;订单是一种会员 ❌(订单只是"属于"会员,不是"是一种")。
super():肩膀上再加一层
子类想扩展父类的 __init__(而不是完全替换)——比如金卡多一个 since(开卡年份)属性。这时用 super() 喊父类来干它那份活:
super().__init__(...) 读作"调用父类的 __init__"。规矩很简单:子类自己写了 __init__,父类那份就不自动跑了——所以第一行先 super().__init__() 把公共装配做完,再补自己的。覆盖其他方法时同理,想"父类的逻辑 + 一点加料"就先 super().方法()。
什么时候不用继承
继承好用,但它是强绑定——父类改一行,所有子类跟着震动。两条克制原则:
- "是一种"才继承。只是想复用几个方法?把公共逻辑抽成普通函数,或者让一个类持有另一个类的对象(组合):订单持有会员(
self.member = member),而不是继承会员 - 层数别超过两层。爷孙三代以上的继承树,读代码的人要上下翻页拼凑一个方法到底是谁的——那是"屠龙技"走火入魔的信号
说明
一句行话预习:社区常说"组合优于继承"。你现阶段的操作版是:默认用组合(属性里装对象),只有"X 是一种 Y"理直气壮时才继承。这一课的分寸感,比语法本身值钱。
为什么这对转行 AI 很重要
打开任何 PyTorch 教程,第一段代码都是:
# 示意代码:阶段 4 才会真正运行它,今天只认结构
import torch.nn as nn
class MyModel(nn.Module): # 你的模型"是一种"神经网络模块
def __init__(self):
super().__init__() # 先让 nn.Module 完成它的装配
# ……定义你的网络层
眼熟吗?class 子类(父类) + super().__init__()——和你今天写的金卡会员一模一样。框架的玩法就是:它提供功能齐全的父类,你继承它、只写自己的差异。今天这 50 分钟,是替阶段 4 的你提前拆掉一颗"看不懂框架代码"的地雷。
✍️ 练习
练习 1:读代码,判输出
先预测四行输出,再运行验证。想清楚每个方法用的是谁家的版本。
✅ 参考答案
……、汪!、我是神秘生物,我说:……、我是旺财,我说:汪!。最妙的是第四行:intro 是父类的方法,但它内部调用 self.sound() 时,self 是旺财——所以用的是 Dog 覆盖后的版本。父类的流程,子类的细节——这正是框架"你继承我、我调用你"的原理。
练习 2:实习生类
已有 Employee 类。写一个 Intern(实习生)继承它:覆盖 pay() 返回固定 3000;新增 mentor 属性(带导师名,用 super() 处理公共部分);新增方法 ask() 返回 "请教 {导师}:这段代码怎么读?"。
💡 提示
实习生的 __init__ 收 name 和 mentor(工资不用收——pay 已覆盖成固定值,salary 传 0 即可):super().__init__(name, 0),然后 self.mentor = mentor。
✅ 参考答案
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def pay(self):
return self.salary
class Intern(Employee):
def __init__(self, name, mentor):
super().__init__(name, 0) # 公共装配交给父类
self.mentor = mentor
def pay(self): # 覆盖:实习补贴固定
return 3000
def ask(self):
return f"请教 {self.mentor}:这段代码怎么读?"
i = Intern("小陈", mentor="老周")
print(i.name, i.pay())
print(i.ask())
练习 3:继承还是组合?(挑战,无代码)
对下面四个场景做判断:该用继承(谁继承谁)还是组合(谁持有谁)?想好再看答案。
- 系统里有"用户",要加"管理员"(多一个封禁他人的权限)
- "汽车"和"发动机"
- "客服工单"和"客户"
- "电子书"和"书"
✅ 参考答案
- 继承:管理员是一种用户——
class Admin(User) - 组合:汽车不是一种发动机,是"有一台"——
self.engine = engine - 组合:工单属于客户但不是一种客户——
self.customer = customer - 继承:电子书是一种书——
class Ebook(Book)(覆盖"翻页",新增"调字号")
口诀验证一遍就不会错:"是一种" → 继承;"有一个" → 组合。
📝 随堂测验
1. class GoldMember(Member): 括号里的 Member 表示什么?
2. 子类定义了和父类同名的方法,调用子类对象的该方法时会发生什么?
3. 子类 __init__ 的第一行常写 super().__init__(...),它在做什么?
4. “订单和会员”的关系,正确的建模是?
5. PyTorch 里 class MyModel(nn.Module) 加 super().__init__(),用本课的话说是在干什么?
本课小结
class 子类(父类):全套继承;覆盖同名方法换细节,新增方法加能力,没写的沿用父类super().__init__(...):请父类先装配公共部分,再补自己的- 分寸感:"是一种"才继承,"有一个"用组合;继承层数别超两层
- 父类流程 + 子类细节(
intro调self.sound())= 框架的工作原理;PyTorch 的nn.Module就是这么用的
下一课换个身份——从"写类的人"变成"读类的人":拿真实标准库当标本,练出看懂文档和源码片段的硬功夫。
划选正文任意文字可高亮、批注或加入复习卡
讨论
载入中…