学径XUEJING · 个人学习平台通知设置

课程库 AI 主线 面向对象初步 › 第 3

继承的直觉 —— 够用即可,不深挖

普通课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 要改两处,必忘一处。

继承:只写不同的部分

Python

拆解四个动作:

  • class 子类(父类):括号一填,父类的属性和方法全部自动继承——GoldMember 生下来就会 __init__add_points
  • ② 覆盖(override):子类写一个同名方法,就用自己的版本替换父类的——金卡的 discount 说了算
  • ③ 新增:子类随便加自己的新方法,父类和兄弟类都不受影响
  • ④⑤ 没写的部分:一律沿用父类——这正是"只写不同的部分"

提示

类比记忆:继承是车型的衍生。基础款(父类)定义了发动机和四个轮子;运动款(子类)是一种基础款——换了悬挂(覆盖),加了尾翼(新增),没动的部分照单全收。判断能不能继承就问一句:"X 是一种 Y 吗?"——金卡会员是一种会员 ✅;订单是一种会员 ❌(订单只是"属于"会员,不是"是一种")。

super():肩膀上再加一层

子类想扩展父类的 __init__(而不是完全替换)——比如金卡多一个 since(开卡年份)属性。这时用 super() 喊父类来干它那份活:

Python

super().__init__(...) 读作"调用父类的 __init__"。规矩很简单:子类自己写了 __init__,父类那份就不自动跑了——所以第一行先 super().__init__() 把公共装配做完,再补自己的。覆盖其他方法时同理,想"父类的逻辑 + 一点加料"就先 super().方法()

什么时候不用继承

继承好用,但它是强绑定——父类改一行,所有子类跟着震动。两条克制原则:

  • "是一种"才继承。只是想复用几个方法?把公共逻辑抽成普通函数,或者让一个类持有另一个类的对象(组合):订单持有会员(self.member = member),而不是继承会员
  • 层数别超过两层。爷孙三代以上的继承树,读代码的人要上下翻页拼凑一个方法到底是谁的——那是"屠龙技"走火入魔的信号
Python

说明

一句行话预习:社区常说"组合优于继承"。你现阶段的操作版是:默认用组合(属性里装对象),只有"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:读代码,判输出

先预测四行输出,再运行验证。想清楚每个方法用的是谁家的版本。

Python
✅ 参考答案

……汪!我是神秘生物,我说:……我是旺财,我说:汪!。最妙的是第四行:intro 是父类的方法,但它内部调用 self.sound() 时,self 是旺财——所以用的是 Dog 覆盖后的版本。父类的流程,子类的细节——这正是框架"你继承我、我调用你"的原理。

练习 2:实习生类

已有 Employee 类。写一个 Intern(实习生)继承它:覆盖 pay() 返回固定 3000;新增 mentor 属性(带导师名,用 super() 处理公共部分);新增方法 ask() 返回 "请教 {导师}:这段代码怎么读?"

Python
💡 提示

实习生的 __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:继承还是组合?(挑战,无代码)

对下面四个场景做判断:该用继承(谁继承谁)还是组合(谁持有谁)?想好再看答案。

  1. 系统里有"用户",要加"管理员"(多一个封禁他人的权限)
  2. "汽车"和"发动机"
  3. "客服工单"和"客户"
  4. "电子书"和"书"
✅ 参考答案
  1. 继承:管理员是一种用户——class Admin(User)
  2. 组合:汽车不是一种发动机,是"有一台"——self.engine = engine
  3. 组合:工单属于客户但不是一种客户——self.customer = customer
  4. 继承:电子书是一种书——class Ebook(Book)(覆盖"翻页",新增"调字号")

口诀验证一遍就不会错:"是一种" → 继承;"有一个" → 组合

📝 随堂测验

随堂测验0 / 5 题正确

1. class GoldMember(Member): 括号里的 Member 表示什么?

2. 子类定义了和父类同名的方法,调用子类对象的该方法时会发生什么?

3. 子类 __init__ 的第一行常写 super().__init__(...),它在做什么?

4. “订单和会员”的关系,正确的建模是?

5. PyTorch 里 class MyModel(nn.Module) 加 super().__init__(),用本课的话说是在干什么?

本课小结

  • class 子类(父类):全套继承;覆盖同名方法换细节,新增方法加能力,没写的沿用父类
  • super().__init__(...):请父类先装配公共部分,再补自己的
  • 分寸感:"是一种"才继承,"有一个"用组合;继承层数别超两层
  • 父类流程 + 子类细节(introself.sound())= 框架的工作原理;PyTorch 的 nn.Module 就是这么用的

下一课换个身份——从"写类的人"变成"读类的人":拿真实标准库当标本,练出看懂文档和源码片段的硬功夫。

划选正文任意文字可高亮、批注或加入复习卡

讨论

载入中…