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(类与迭代器/生成器)构成入门阶段最后的理论内容,之后进入实践型课程。