跳转至

11.3 函数进阶与类 —— 类

内容梗概

本课快速介绍 Python 中类(class)的基本用法,并借类的概念讲清迭代器、生成器和生成器表达式。老师明确说明:面向对象编程本身是一门约 48 学时的独立课程,本课只要求大家在阅读代码时能认出并理解面向对象的写法,不要求会用面向对象思维独立编程。主要内容包括:类的定义(属性、方法、文档字符串)、__init__ 构造函数与 self、类与对象(实例)的关系;随后是"可迭代对象"的判定标准(__iter__ 与 __next__ 两个方法)、用类实现倒序迭代器的完整示例、用 yield 写生成器实现同样功能,以及生成器表达式的简化写法。最后回答了一位同学关于 __iter__ 为什么 return self 的问题。

知识点详解

课程定位 (约 00:00 - 00:18)

  • 这部分讲得比较快,不需要太多绕弯思考。
  • 目的不是讲面向对象编程(那应是一门独立课程),而是讲 Python 里类的写法,让大家遇到面向对象风格的代码时能理解其逻辑关系。

类的基本定义 (约 00:18 - 01:30)

  • 用 class 关键字定义类,如 class MyClass:。
  • 类体里可以有:
  • 文档字符串:与函数的文档字符串一样;
  • 属性:类里定义的变量;
  • 方法:类里定义的函数。
  • 面向对象的基本概念:把某一类事物抽象成类。例:class Human 可有性别、年龄、身高、体重等属性,走路、挥拳等方法——相关的特征是属性,相关的操作是方法。

__init__ 构造函数与 self (约 01:30 - 04:20)

  • 再次重申:Python 中**两个下划线夹起来的函数/变量是内置的特殊函数/变量**。
  • 示例:定义 class Hero(游戏英雄),属性有位置(如 carry)和名字 name。
  • 建议写法:先在类里给 name 定义一个空字符串作为占位,再通过初始化函数在创建对象时传入特定字符串。
  • __init__ 构造函数:用类创建对象时,传入的参数会作为 __init__ 的第二个参数(第一个参数是 self)传入;self.name = name 把传入的值赋给对象的属性。
  • 之后通过对象的 .name 即可访问创建时赋进去的名字。

类与对象(实例)的关系 (约 04:20 - 10:00)

  • burning = Hero("敌法师") 这行代码称为**初始化一个 Hero 类的对象**:Hero 是类,burning 是这个类的对象(也叫"实例",两种称呼一回事,"对象"更官方)。
  • 比喻:类是**图纸**,对象是按图纸做出来的一个个具体的东西。如 Human 类对应 70 亿不同的人(实例),每个实例的 name、身高、体重各不相同,但都拥有这些特征。
  • 同一个类可以有多个不同的实例;通过对象可以访问类里定义的所有属性和方法。
  • 另一个示例:class Programmer 有典型属性"是否秃头",默认秃头;创建"安静"这个实例时名字为安静、且没有秃头(老师自嘲现在也快秃了)。
  • 强调:想真正会写面向对象代码需要专门学习和大量练习;听得似懂非懂不是天赋问题,主要是花的时间不够,需要自己扩展学习。

迭代器与可迭代对象 (约 10:02 - 19:10)

  • 回收伏笔:之前讲 list(...) 的参数、for i in ...: 后面的对象时,多次说"这里必须是可迭代的对象,等讲面向对象时再解释",现在兑现。
  • 可迭代对象的判定:一个对象所属的类提供两个方法即可——
  • __iter__(迭代方法):只传一个参数 self(对象本身);
  • __next__(next 方法):每次迭代时被不断调用。
  • __next__ 要满足两个条件:
  • 每次返回自己内部的一个元素(如内部 data 序列中某个 index 的元素);
  • 有停止迭代的情况——迭代结束时**抛出名为"停止迭代"(StopIteration)的异常**,作为循环的终止条件。
  • 倒序迭代器类示例 Reverse(约 14:36):
  • 构造函数 __init__ 接收一个序列 data(列表、字符串均可;元组是不可变序列不合适),存为属性 self.data,并把当前索引 index 初始化为 len(data)(指向序列末尾之外的空位)。
  • __iter__ 返回 self(把自己作为迭代器返回)。
  • __next__ 每次把 index 前移一位并返回对应元素,于是实现**倒序遍历**:第一次返回最后一个元素,再依次向前;索引用尽时抛出停止迭代异常。
  • 使用:r = Reverse(某序列),for i in r: print(i) 即倒序输出;也可先 r = Reverse(data) 再迭代,或缩写为一行,结果一致。
  • 判断一个对象可不可迭代:看它的类有没有 __iter__ 和 __next__ 这两个方法且能正常工作;for、list() 等操作遇到停止迭代异常时正常结束。

生成器(generator)(约 19:31 - 23:50)

  • 生成器长得像函数但没有 return,而是用 yield 关键字;它不叫函数,叫生成器(generator)。
  • 工作方式:每次迭代到 yield 就返回一个元素;再次迭代时从上次 yield 的位置继续执行,返回下一个元素。
  • 用生成器实现同样的倒序逻辑:只需一个参数(序列),内部一个倒序的 for 循环里 yield 每个元素即可,比写迭代器类简单得多。
  • 生成器**自动创建了 __iter__ 和 __next__ 两个方法**,自动记录局部变量(如 index)上一次的位置,遍历结束时自动触发停止迭代逻辑。
  • 生成器相对迭代器:写法更简单,但人脑理解其执行流程可能稍复杂;它是一种"语法糖"——没有它用迭代器也能实现同样功能,但在实际代码中经常看到。

生成器表达式 (约 23:50 - 27:30)

  • 生成器可进一步简化为**生成器表达式**,形式像列表推导式但用圆括号。
  • 对比:列表推导式 [i**2 for i in range(10)] 生成 10 以内数字平方的列表。
  • 内置函数 sum() 接收一个**可迭代对象**,把所有元素累加。
  • 求"10 以内数字平方和"的三种写法:
  • 先用列表推导式生成列表 a,再 sum(a);
  • 定义一个生成器函数(如 gen(number),内部 for i in range(number): yield i**2),再 sum(gen(10));
  • 最简:直接把生成器表达式传进去 sum(i**2 for i in range(10))。
  • 生成器表达式可直接传给任何需要可迭代对象的函数。
  • 老师强调:这个内容记不住没关系,只要留下"有这么回事"的浅印象,将来见到时知道它叫生成器表达式、去查去学即可。

答疑:__iter__ 为什么 return self (约 27:36 - 31:55)

  • 迭代协议的两步:对可迭代对象 A 做 for i in A 或 list(A) 时,Python 先调用 A.__iter__() 得到一个对象 B,再不断调用 B.__next__() 进行真正的迭代。
  • 因此存在两种设计:
  • __iter__ 里**创建并返回一个新对象 B**,B 必须有 __next__ 方法——此时 A 本身可以没有 __next__("套娃"式结构);
  • 本课示例"偷懒"的做法:A 本身就有 __next__,但 __iter__ 不能省略,于是在 __iter__ 里直接 return self(返回 A 自己),后续直接对 A 调 __next__ 即可。
  • 所以 return self 是因为这个类的对象自身同时具备两个方法,正好返回自己满足协议。

示例与演示

  • 类的定义与实例化:class Hero,属性位置(carry)、名字 name;burning = Hero("敌法师") 创建对象后访问 .name。
  • Programmer 类:属性"是否秃头"默认 True,创建实例"安静"(不秃头),说明类与实例的关系。
  • 倒序迭代器类 Reverse:实现 __init__ / __iter__ / __next__,for i in Reverse(名字字符串) 倒序打印每个字符;演示了拆分写法和一行简写。
  • 生成器版倒序:用 yield 的生成器函数实现与 Reverse 类相同的功能。
  • 生成器表达式:sum(i**2 for i in range(10)) 求平方和,与列表推导式、生成器函数两种写法对比。
  • 课堂答疑:解释迭代协议中 __iter__ 返回 self 与返回新对象两种模式的区别。

重点与难点

  • 类 vs 对象(实例):类是图纸,对象是实例;实例化就是按类创建对象的过程;一个类可有多个实例,各自属性值不同。
  • __init__ 的参数传递:创建对象时传的实参对应 __init__ 的第二个及以后参数,self 指对象自身;self.name = name 把参数存成对象属性。
  • 双下划线名字是 Python 内置的特殊方法/变量(如 __init__、__iter__、__next__),不要自己乱起这种名字。
  • 可迭代对象的本质:有 __iter__ 和 __next__ 两个方法;__next__ 必须在迭代结束时抛出 StopIteration 异常,否则循环无法终止。
  • 迭代是两步协议:先 __iter__() 拿迭代器,再 __next__() 逐个取元素;return self 只是自身兼任迭代器的偷懒写法。
  • 生成器是语法糖:yield 自动实现了迭代协议并保存执行现场,写法简单但执行逻辑要动脑想清楚。
  • 本课只要求"看得懂",不要求会用面向对象思维写代码。

关联内容

  • 前置知识:函数定义(11.1 函数进阶)、作用域(11.2,类方法中的 self 与属性访问与作用域密切相关)、序列与列表、for 循环、list() 函数、列表推导式(之前课程讲列表推导式时埋下伏笔)。
  • 回收伏笔:之前课程多次提到"可迭代对象"概念时说"讲到面向对象再解释",本课兑现。
  • 面向对象编程本身是独立课程(约 48 学时),本课只是 Python 视角的入门介绍。
  • 与后续实验课的关系:第三方库和开源项目大量使用面向对象写法、迭代器和生成器(生成器表达式在真实代码中常见),本课知识用于读懂这些代码。
  • 本章小结:11.1(函数进阶)→ 11.2(作用域)→ 11.3(类与迭代器/生成器)构成入门阶段最后的理论内容,之后进入实践型课程。