Files
PythonLearn/02_python进阶/2_5_迭代器与生成器/README.md

9.6 KiB
Raw Blame History

第 2-5 课:迭代器与生成器

一、本课目标

完成本课后,你将能够:

  1. 解释可迭代对象、迭代器和生成器分别是什么;
  2. 使用 iter() 创建迭代器;
  3. 使用 next() 逐个取得数据;
  4. 理解数据取完时出现的 StopIteration
  5. 使用包含 yield 的生成器函数逐个产生结果;
  6. 说明生成器为什么适合处理大量数据;
  7. 理解同一个生成器被消费后不能自动回到开头。

二、前置知识

学习本课前,需要掌握:

  • 列表、字典和集合;
  • for 循环和 while 循环;
  • 函数、参数和返回值;
  • try...except 异常处理;
  • 列表推导式。

三、从 for 循环开始理解

我们已经多次使用 for 遍历列表:

agent_names = ["代码助手", "聊天助手", "测试助手"]

for agent_name in agent_names:
    print(agent_name)

列表可以依次提供其中的数据因此它是可迭代对象Iterable。可迭代对象可以暂时理解为“能够被 for 循环逐项访问的数据”。

目前学过的常见可迭代对象包括:

  • 字符串;
  • 列表;
  • 元组;
  • 字典;
  • 集合;
  • range() 产生的对象。

四、迭代器是什么

迭代器Iterator是记录当前遍历位置并能提供下一个值的对象。

使用 iter() 可以根据可迭代对象创建迭代器:

agent_names = ["代码助手", "聊天助手"]
name_iterator = iter(agent_names)

使用 next() 逐个取得值:

print(next(name_iterator))
print(next(name_iterator))

输出:

代码助手
聊天助手

每调用一次 next(),迭代器都会向后移动一步。它不会自动回到开头。

五、StopIteration

当迭代器中的数据全部取完后,再调用 next() 会出现:

StopIteration

停止迭代异常Stop IterationStopIteration)表示迭代器已经没有下一个值。

可以手动处理:

try:
    print(next(name_iterator))
except StopIteration:
    print("数据已经全部取完。")

for 循环实际上会在内部不断取得下一个值,并在遇到 StopIteration 时自动结束,所以平时使用 for 不需要自己捕获它。

六、手动还原 for 循环的过程

下面两段代码表达相似的过程。

使用 for

for value in values:
    print(value)

手动使用迭代器:

value_iterator = iter(values)

while True:
    try:
        value = next(value_iterator)
    except StopIteration:
        break

    print(value)

真实的 for 循环由 Python 自动完成这些步骤。本课手动编写一次,是为了理解其工作方式,而不是建议以后都替换 for

七、生成器是什么

生成器Generator是一种特殊的迭代器。它可以按需要逐个产生数据不必先把全部结果存入列表。

包含 yield 的函数称为生成器函数:

def generate_numbers():
    yield 1
    yield 2
    yield 3

调用普通函数时函数会立即执行并返回结果。调用生成器函数时Python 先创建生成器对象:

number_generator = generate_numbers()

这时函数体还没有完整执行。可以用 next()for 逐个取得值:

print(next(number_generator))
print(next(number_generator))

八、yieldreturn 的区别

普通函数使用 return

def get_names(agents):
    names = []

    for agent in agents:
        names.append(agent["name"])

    return names

它会先准备完整列表,然后一次返回。

生成器函数使用 yield

def generate_names(agents):
    for agent in agents:
        yield agent["name"]

yield 会完成三件事:

  1. 交出当前结果;
  2. 暂停函数;
  3. 保存当前执行位置,下一次继续从这里向后运行。

函数中的 return 会结束整个函数;yield 交出一个值后还能继续生成后面的值。

九、使用 for 遍历生成器

生成器也是迭代器,因此可以直接使用 for

for agent_name in generate_names(agents):
    print(agent_name)

这种写法最常见,也比手动调用 next() 更安全。

如果确实需要把所有结果放入列表,可以使用:

agent_names = list(generate_names(agents))

注意:这样会立即消费生成器,并把全部结果保存到内存中。

十、为什么需要生成器

假设需要处理一百万条记录。列表写法通常先准备一百万个结果,再开始使用;生成器可以产生一条、处理一条。

这种按需产生数据的方式称为惰性求值Lazy Evaluation。这里的“惰性”不是运行缓慢而是“需要时才计算”。

生成器的主要优点:

  • 不必一次保存全部结果;
  • 可以更早开始处理第一条数据;
  • 适合文件逐行读取、大量查询结果或持续数据流。

本课使用少量 Agent 数据是为了便于观察。数据量很小时,普通列表通常同样合适。

十一、生成器只能继续向后执行

name_generator = generate_names(agents)

first_result = list(name_generator)
second_result = list(name_generator)

第一次已经把生成器消费完,因此第二次得到空列表。

如果确实需要重新遍历,必须重新调用生成器函数创建一个新生成器:

new_generator = generate_names(agents)

十二、生成器表达式

上一课学习了列表推导式:

enabled_names = [
    agent["name"]
    for agent in agents
    if agent["enabled"]
]

把最外层方括号改成圆括号会得到生成器表达式Generator Expression

enabled_name_generator = (
    agent["name"]
    for agent in agents
    if agent["enabled"]
)

列表推导式立即创建完整列表,生成器表达式按需产生结果。本课练习以 yield 为主,先把生成器函数的执行过程理解清楚。

十三、完整示例

示例文件:

02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py

示例依次演示:

  1. 使用 iter()next() 手动取出名称;
  2. 捕获数据取完后的 StopIteration
  3. 使用生成器筛选启用 Agent
  4. 使用生成器逐条产生 Agent 报告。

十四、运行方法

在项目根目录运行示例:

python .\02_python进阶\2_5_迭代器与生成器\iterator_generator_example.py

完成练习后运行:

python .\02_python进阶\2_5_迭代器与生成器\practice.py

十五、预期结果

一、手动使用迭代器
代码助手
聊天助手
测试助手
名称已经全部取完。
==============================
二、使用生成器函数
生成器对象generator
启用 Agent代码助手
启用 Agent测试助手
==============================
三、逐条生成 Agent 报告
代码助手状态启用工具数量2
聊天助手状态停用工具数量1
测试助手状态启用工具数量2

十六、常见错误

16.1 对可迭代对象直接使用 next()

列表是可迭代对象,但列表本身不是迭代器。应先调用:

name_iterator = iter(agent_names)

16.2 数据取完后继续调用 next()

迭代器不会自动回到开头。数据取完后继续调用会产生 StopIteration

16.3 在生成器函数中使用 return result_list

如果先创建完整列表再返回,就没有实现按需生成。需要逐个结果时应使用 yield

16.4 以为调用生成器函数就会立即执行

调用生成器函数只会创建生成器对象。第一次调用 next() 或开始 for 遍历时,函数才真正向前执行。

16.5 重复消费同一个生成器

同一个生成器不会自动重置。第二次遍历已经消费完的生成器不会再次得到原数据。

16.6 所有地方都改用生成器

少量数据需要反复读取时,列表可能更直观。生成器适合按顺序处理、无需返回头部或数据量较大的场景。

十七、课堂练习

打开 practice.py,依次完成:

  1. 使用 iter()next() 取得前两个名称;
  2. 手动捕获 StopIteration 并消费迭代器;
  3. 使用 yield 生成启用 Agent 名称;
  4. 使用 yield 生成所有工具名称;
  5. 观察同一个生成器被消费两次的结果;
  6. main() 中输出并核对全部结果。

十八、参考答案

参考答案暂不写入练习文件。完成后,我会从正确性、可读性和知识掌握情况三个方面验证,并检查生成器函数是否真正使用 yield 按需产生数据。

十九、本课小结

  • 可迭代对象能被 for 逐项访问;
  • 迭代器记录当前遍历位置;
  • iter() 创建迭代器,next() 取得下一个值;
  • 数据取完时会出现 StopIteration
  • for 循环会自动处理停止迭代;
  • 包含 yield 的函数是生成器函数;
  • yield 交出一个结果并暂停当前函数;
  • 生成器按需产生数据,适合顺序处理大量内容;
  • 同一个生成器消费完后不能自动重新开始。

二十、验收标准

  • 能解释可迭代对象与迭代器的区别;
  • 能使用 iter()next() 手动取得数据;
  • 能正确处理 StopIteration
  • 能解释 for 如何自动结束迭代;
  • 能编写包含 yield 的生成器函数;
  • 能使用 forlist() 消费生成器;
  • 能解释 yieldreturn 的区别;
  • 能说明同一个生成器第二次消费为什么为空;
  • 生成器函数没有提前创建完整结果列表;
  • 示例与练习均不会修改原始 Agent 数据。