feat(python进阶): 完成第三至第八课教学内容
This commit is contained in:
371
02_python进阶/2_5_迭代器与生成器/README.md
Normal file
371
02_python进阶/2_5_迭代器与生成器/README.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# 第 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 数据。
|
||||
74
02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py
Normal file
74
02_python进阶/2_5_迭代器与生成器/iterator_generator_example.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# 第 2-5 课完整示例:迭代器与生成器
|
||||
#
|
||||
# 本示例使用少量固定数据观察迭代过程。
|
||||
# 重点不是缩短代码,而是理解数据如何被逐个取出和按需生成。
|
||||
|
||||
|
||||
def generate_enabled_agent_names(agents):
|
||||
"""逐个生成处于启用状态的 Agent 名称。"""
|
||||
for agent in agents:
|
||||
if agent["enabled"]:
|
||||
# yield 每次只交出一个结果,并暂停函数当前的执行位置。
|
||||
yield agent["name"]
|
||||
|
||||
|
||||
def generate_agent_reports(agents):
|
||||
"""逐个生成 Agent 的中文报告。"""
|
||||
for agent in agents:
|
||||
status_text = "启用" if agent["enabled"] else "停用"
|
||||
tool_count = len(agent.get("tools", []))
|
||||
yield (
|
||||
f"{agent['name']}|状态:{status_text}|"
|
||||
f"工具数量:{tool_count}"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""演示 iter()、next()、StopIteration 和生成器。"""
|
||||
agents = [
|
||||
{
|
||||
"name": "代码助手",
|
||||
"enabled": True,
|
||||
"tools": ["搜索", "终端"],
|
||||
},
|
||||
{
|
||||
"name": "聊天助手",
|
||||
"enabled": False,
|
||||
"tools": ["搜索"],
|
||||
},
|
||||
{
|
||||
"name": "测试助手",
|
||||
"enabled": True,
|
||||
"tools": ["终端", "测试"],
|
||||
},
|
||||
]
|
||||
|
||||
print("一、手动使用迭代器")
|
||||
agent_names = [agent["name"] for agent in agents]
|
||||
name_iterator = iter(agent_names)
|
||||
|
||||
print(next(name_iterator))
|
||||
print(next(name_iterator))
|
||||
print(next(name_iterator))
|
||||
|
||||
try:
|
||||
next(name_iterator)
|
||||
except StopIteration:
|
||||
print("名称已经全部取完。")
|
||||
|
||||
print("=" * 30)
|
||||
print("二、使用生成器函数")
|
||||
enabled_name_generator = generate_enabled_agent_names(agents)
|
||||
print(f"生成器对象:{type(enabled_name_generator).__name__}")
|
||||
|
||||
for agent_name in enabled_name_generator:
|
||||
print(f"启用 Agent:{agent_name}")
|
||||
|
||||
print("=" * 30)
|
||||
print("三、逐条生成 Agent 报告")
|
||||
for report in generate_agent_reports(agents):
|
||||
print(report)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
73
02_python进阶/2_5_迭代器与生成器/practice.py
Normal file
73
02_python进阶/2_5_迭代器与生成器/practice.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# 第 2-5 课课堂练习:迭代器与生成器
|
||||
#
|
||||
# 请先阅读讲义并运行完整示例,再按照题目顺序完成。
|
||||
# 需要生成多个结果的函数必须使用 yield,不要提前创建完整结果列表。
|
||||
# 不要删除题目、测试数据、预期结果和自查注释。
|
||||
|
||||
|
||||
# 第一部分:手动使用迭代器
|
||||
# 1. 定义 show_first_two_names(agents) 函数。
|
||||
# 2. 使用列表推导式取得所有 Agent 名称。
|
||||
# 3. 使用 iter() 把名称列表转换成迭代器,并保存为 name_iterator。
|
||||
# 4. 连续调用两次 next(),分别保存第一个和第二个名称。
|
||||
# 5. return 一个包含两个名称的元组。
|
||||
# 6. 调用函数并保存结果,预期返回 ("代码助手", "聊天助手")。
|
||||
|
||||
# 第二部分:处理 StopIteration
|
||||
# 1. 定义 consume_iterator(values) 函数,参数接收列表。
|
||||
# 2. 使用 iter(values) 创建迭代器。
|
||||
# 3. 使用 while True 持续调用 next(),把每个结果加入 result 列表。
|
||||
# 4. 捕获 StopIteration 后使用 break 结束循环。
|
||||
# 5. return result。
|
||||
# 6. 传入 ["搜索", "终端"],预期返回相同内容的新列表。
|
||||
# 7. 传入空列表 [],预期返回 [],程序不能崩溃。
|
||||
|
||||
# 第三部分:定义 generate_enabled_names(agents) 生成器函数
|
||||
# 1. 使用 for 遍历 agents。
|
||||
# 2. 只处理 enabled 为 True 的 Agent。
|
||||
# 3. 使用 yield 逐个生成 Agent 名称,不要创建结果列表。
|
||||
# 4. 调用函数时先把生成器对象保存为 enabled_name_generator。
|
||||
# 5. 使用 list(enabled_name_generator) 收集结果并输出。
|
||||
# 6. 预期结果:['代码助手', '测试助手']。
|
||||
|
||||
# 第四部分:定义 generate_tool_names(agents) 生成器函数
|
||||
# 1. 使用两层 for:外层遍历 agents,内层遍历 agent.get("tools", [])。
|
||||
# 2. 使用 yield 逐个生成工具名称。
|
||||
# 3. 本练习不去重,重复工具也需要保留。
|
||||
# 4. 使用 list() 收集生成结果。
|
||||
# 5. 预期结果:['搜索', '终端', '搜索', '终端', '测试']。
|
||||
|
||||
# 第五部分:观察生成器只能继续向后取值
|
||||
# 1. 调用 generate_enabled_names(agents) 创建一个新生成器。
|
||||
# 2. 第一次使用 list() 收集并保存结果,预期有两个名称。
|
||||
# 3. 对同一个生成器再次使用 list(),预期得到空列表。
|
||||
# 4. 输出两次结果,并用自己的话解释第二次为什么为空。
|
||||
# 注意:不要为了让第二次也有数据而重新创建生成器,本题就是观察同一个生成器。
|
||||
|
||||
# 第六部分:定义 main() 函数
|
||||
# 1. 依次调用前面四个函数,并把返回值或生成结果保存到有意义的变量中。
|
||||
# 2. 输出前两个名称、迭代器消费结果、启用名称和全部工具名称。
|
||||
# 3. 完成第五部分的两次生成器收集实验。
|
||||
# 4. 添加程序入口判断,直接运行本文件时调用 main()。
|
||||
|
||||
|
||||
# 最终验收测试:
|
||||
# 1. show_first_two_names(agents) 返回正确的两个名称;
|
||||
# 2. consume_iterator(["搜索", "终端"]) 返回相同内容的新列表;
|
||||
# 3. consume_iterator([]) 返回空列表且不报错;
|
||||
# 4. generate_enabled_names() 是包含 yield 的生成器函数;
|
||||
# 5. 启用名称生成结果为代码助手和测试助手;
|
||||
# 6. generate_tool_names() 是包含 yield 的生成器函数;
|
||||
# 7. 工具生成结果保留重复的搜索和终端;
|
||||
# 8. 同一个生成器第一次收集有数据,第二次收集为空;
|
||||
# 9. 生成器函数中没有先创建完整结果列表再 return。
|
||||
|
||||
|
||||
# 完成后自查:
|
||||
# 1. 是否能解释可迭代对象和迭代器的区别;
|
||||
# 2. 是否知道 iter() 创建迭代器、next() 取出下一个值;
|
||||
# 3. 是否知道数据取完后会出现 StopIteration;
|
||||
# 4. 是否理解 for 循环会自动处理 StopIteration;
|
||||
# 5. 是否理解 yield 会交出结果并暂停函数;
|
||||
# 6. 是否知道生成器只能从当前位置继续向后执行;
|
||||
# 7. 是否保留了完整题目和验收说明。
|
||||
Reference in New Issue
Block a user