Files

372 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 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 数据。