# 第 2-5 课:迭代器与生成器 ## 一、本课目标 完成本课后,你将能够: 1. 解释可迭代对象、迭代器和生成器分别是什么; 2. 使用 `iter()` 创建迭代器; 3. 使用 `next()` 逐个取得数据; 4. 理解数据取完时出现的 `StopIteration`; 5. 使用包含 `yield` 的生成器函数逐个产生结果; 6. 说明生成器为什么适合处理大量数据; 7. 理解同一个生成器被消费后不能自动回到开头。 ## 二、前置知识 学习本课前,需要掌握: - 列表、字典和集合; - `for` 循环和 `while` 循环; - 函数、参数和返回值; - `try...except` 异常处理; - 列表推导式。 ## 三、从 `for` 循环开始理解 我们已经多次使用 `for` 遍历列表: ```python agent_names = ["代码助手", "聊天助手", "测试助手"] for agent_name in agent_names: print(agent_name) ``` 列表可以依次提供其中的数据,因此它是可迭代对象(Iterable)。可迭代对象可以暂时理解为“能够被 `for` 循环逐项访问的数据”。 目前学过的常见可迭代对象包括: - 字符串; - 列表; - 元组; - 字典; - 集合; - `range()` 产生的对象。 ## 四、迭代器是什么 迭代器(Iterator)是记录当前遍历位置,并能提供下一个值的对象。 使用 `iter()` 可以根据可迭代对象创建迭代器: ```python agent_names = ["代码助手", "聊天助手"] name_iterator = iter(agent_names) ``` 使用 `next()` 逐个取得值: ```python print(next(name_iterator)) print(next(name_iterator)) ``` 输出: ```text 代码助手 聊天助手 ``` 每调用一次 `next()`,迭代器都会向后移动一步。它不会自动回到开头。 ## 五、`StopIteration` 当迭代器中的数据全部取完后,再调用 `next()` 会出现: ```text StopIteration ``` 停止迭代异常(Stop Iteration,`StopIteration`)表示迭代器已经没有下一个值。 可以手动处理: ```python try: print(next(name_iterator)) except StopIteration: print("数据已经全部取完。") ``` `for` 循环实际上会在内部不断取得下一个值,并在遇到 `StopIteration` 时自动结束,所以平时使用 `for` 不需要自己捕获它。 ## 六、手动还原 `for` 循环的过程 下面两段代码表达相似的过程。 使用 `for`: ```python for value in values: print(value) ``` 手动使用迭代器: ```python value_iterator = iter(values) while True: try: value = next(value_iterator) except StopIteration: break print(value) ``` 真实的 `for` 循环由 Python 自动完成这些步骤。本课手动编写一次,是为了理解其工作方式,而不是建议以后都替换 `for`。 ## 七、生成器是什么 生成器(Generator)是一种特殊的迭代器。它可以按需要逐个产生数据,不必先把全部结果存入列表。 包含 `yield` 的函数称为生成器函数: ```python def generate_numbers(): yield 1 yield 2 yield 3 ``` 调用普通函数时,函数会立即执行并返回结果。调用生成器函数时,Python 先创建生成器对象: ```python number_generator = generate_numbers() ``` 这时函数体还没有完整执行。可以用 `next()` 或 `for` 逐个取得值: ```python print(next(number_generator)) print(next(number_generator)) ``` ## 八、`yield` 与 `return` 的区别 普通函数使用 `return`: ```python def get_names(agents): names = [] for agent in agents: names.append(agent["name"]) return names ``` 它会先准备完整列表,然后一次返回。 生成器函数使用 `yield`: ```python def generate_names(agents): for agent in agents: yield agent["name"] ``` `yield` 会完成三件事: 1. 交出当前结果; 2. 暂停函数; 3. 保存当前执行位置,下一次继续从这里向后运行。 函数中的 `return` 会结束整个函数;`yield` 交出一个值后还能继续生成后面的值。 ## 九、使用 `for` 遍历生成器 生成器也是迭代器,因此可以直接使用 `for`: ```python for agent_name in generate_names(agents): print(agent_name) ``` 这种写法最常见,也比手动调用 `next()` 更安全。 如果确实需要把所有结果放入列表,可以使用: ```python agent_names = list(generate_names(agents)) ``` 注意:这样会立即消费生成器,并把全部结果保存到内存中。 ## 十、为什么需要生成器 假设需要处理一百万条记录。列表写法通常先准备一百万个结果,再开始使用;生成器可以产生一条、处理一条。 这种按需产生数据的方式称为惰性求值(Lazy Evaluation)。这里的“惰性”不是运行缓慢,而是“需要时才计算”。 生成器的主要优点: - 不必一次保存全部结果; - 可以更早开始处理第一条数据; - 适合文件逐行读取、大量查询结果或持续数据流。 本课使用少量 Agent 数据是为了便于观察。数据量很小时,普通列表通常同样合适。 ## 十一、生成器只能继续向后执行 ```python name_generator = generate_names(agents) first_result = list(name_generator) second_result = list(name_generator) ``` 第一次已经把生成器消费完,因此第二次得到空列表。 如果确实需要重新遍历,必须重新调用生成器函数创建一个新生成器: ```python new_generator = generate_names(agents) ``` ## 十二、生成器表达式 上一课学习了列表推导式: ```python enabled_names = [ agent["name"] for agent in agents if agent["enabled"] ] ``` 把最外层方括号改成圆括号,会得到生成器表达式(Generator Expression): ```python enabled_name_generator = ( agent["name"] for agent in agents if agent["enabled"] ) ``` 列表推导式立即创建完整列表,生成器表达式按需产生结果。本课练习以 `yield` 为主,先把生成器函数的执行过程理解清楚。 ## 十三、完整示例 示例文件: ```text 02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py ``` 示例依次演示: 1. 使用 `iter()` 和 `next()` 手动取出名称; 2. 捕获数据取完后的 `StopIteration`; 3. 使用生成器筛选启用 Agent; 4. 使用生成器逐条产生 Agent 报告。 ## 十四、运行方法 在项目根目录运行示例: ```powershell python .\02_python进阶\2_5_迭代器与生成器\iterator_generator_example.py ``` 完成练习后运行: ```powershell python .\02_python进阶\2_5_迭代器与生成器\practice.py ``` ## 十五、预期结果 ```text 一、手动使用迭代器 代码助手 聊天助手 测试助手 名称已经全部取完。 ============================== 二、使用生成器函数 生成器对象:generator 启用 Agent:代码助手 启用 Agent:测试助手 ============================== 三、逐条生成 Agent 报告 代码助手|状态:启用|工具数量:2 聊天助手|状态:停用|工具数量:1 测试助手|状态:启用|工具数量:2 ``` ## 十六、常见错误 ### 16.1 对可迭代对象直接使用 `next()` 列表是可迭代对象,但列表本身不是迭代器。应先调用: ```python 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` 的生成器函数; - 能使用 `for` 和 `list()` 消费生成器; - 能解释 `yield` 与 `return` 的区别; - 能说明同一个生成器第二次消费为什么为空; - 生成器函数没有提前创建完整结果列表; - 示例与练习均不会修改原始 Agent 数据。