10 迭代器与生成器
📋 资料说明
- 来源:课程 day09 后半 ~ day10 前半
- 笔记:
尚硅谷大模型技术之Python1.0.docx第 11 章 - 每日一考:
day09_每日一考.md、day10_每日一考.md - 视频:❌ 跳过(约 8 个视频:迭代器介绍 → 自定义迭代器 → 生成器表达式 → 生成器函数 → send)
🟢 🤖 AI 替代率:90%
为什么是这个水平:
迭代器和生成器的语法 AI 可以完全生成。但以下概念需要人类理解:
- 迭代器协议:
__iter__和__next__的调用链——人是靠理解协议才能写出正确的自定义迭代器,AI 只是"背"出了模式 - yield 语义:生成器函数执行到
yield时"冻结"状态,下次从yield后面继续执行——这个暂停/恢复机制对新手违反直觉 - send 方法:
generator.send(value)可以向生成器发送数据,恢复执行时yield表达式返回这个值——理解通道双向通信需要人类思考
👤 人工干预率:10% | 人类重点关注
为什么需要人工干预:
- 迭代器是一次性的:遍历完就不能再用,很多人第一次遇到
list(gen)后再次遍历发现为空时会困惑 - 生成器是惰性计算:值在需要时才生成,不是一次性全部算出。这对内存优化有利,但调试时难以直接查看"所有值"
- send/throw/close 的方法族:高级用法,日常用到的机会不多,但了解后能在管道和协程场景中活用
人类与 AI 协作建议:
- 让 AI 写出迭代器和生成器的代码,你用
for循环验证结果 - 把生成器表达式改成列表推导式运行一次,观察区别
- 用
next()单步调试生成器,理解"暂停→恢复"的流程
1. 迭代器
实现了 __iter__ 和 __next__ 方法的对象就是迭代器。
python
# 可迭代对象(可以用 for 循环的)→ 实现了 __iter__()
# 迭代器(可以用 next() 的)→ 实现了 __iter__() 和 __next__()
# for 循环的本质:
my_list = [1, 2, 3]
# 1. 调用 iter(my_list) 获得迭代器
# 2. 反复调用 next(iterator) 获取下一个值
# 3. 遇到 StopIteration 退出
it = iter(my_list) # 获取迭代器
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# print(next(it)) # StopIteration自定义迭代器
python
class CountDown:
"""倒计时迭代器"""
def __init__(self, start):
self.current = start
def __iter__(self):
return self # 迭代器返回自身
def __next__(self):
if self.current <= 0:
raise StopIteration # 结束
value = self.current
self.current -= 1
return value
# 使用
for i in CountDown(5):
print(i) # 5, 4, 3, 2, 12. 生成器
生成器是简化版的迭代器——用 yield 关键字。
生成器函数
python
def count_down(n):
"""生成器函数——包含 yield 关键字"""
while n > 0:
yield n # 返回 n,暂停执行
n -= 1
# 函数结束自动抛出 StopIteration
# 调用生成器函数返回生成器对象
gen = count_down(3)
print(next(gen)) # 3(第一次 yield)
print(next(gen)) # 2(从 yield 后面继续)
print(next(gen)) # 1
# print(next(gen)) # StopIteration
# 生成器可以用 for 循环
for i in count_down(3):
print(i)yield 的执行流程
python
def demo():
print("1. 开始执行")
yield "第一次暂停"
print("2. 从这里继续")
yield "第二次暂停"
print("3. 结束")
gen = demo()
print(next(gen)) # → 1. 开始执行 → "第一次暂停"
print(next(gen)) # → 2. 从这里继续 → "第二次暂停"
print(next(gen)) # → 3. 结束 → StopIteration生成器的应用
python
# 1. 无限序列(节省内存)
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib), end=" ") # 0 1 1 2 3 5 8 13 21 34
# 2. 读取大文件(逐行处理)
def read_large_file(filename):
with open(filename, "r", encoding="utf-8") as f:
for line in f:
yield line.strip()生成器表达式
python
# 语法类似列表推导式,但用 () 而不是 []
list_comp = [x**2 for x in range(10)] # 立即计算,占用内存
gen_expr = (x**2 for x in range(10)) # 惰性计算,节省内存
print(list_comp) # [0, 1, 4, ..., 81]
print(gen_expr) # <generator object <genexpr> at ...>
# 生成器表达式逐个取值
for val in gen_expr:
print(val, end=" ") # 0 1 4 9 16 25 36 49 64 81
# 生成器只可遍历一次
gen = (x for x in [1, 2, 3])
print(sum(gen)) # 6
print(sum(gen)) # 0(已经空了!)3. send 方法
send() 可以向生成器发送数据,生成器内部通过 yield 表达式接收。
python
def echo():
"""回声生成器——接收数据并打印"""
print("等待数据...")
while True:
received = yield # yield 表达式接收 send 发送的值
print(f"收到: {received}")
gen = echo()
next(gen) # 启动生成器(执行到第一个 yield)→ "等待数据..."
gen.send("Hello") # → "收到: Hello"
gen.send("World") # → "收到: World"
# send 第一次启动需要传 None
# gen.send("start") # ❌ TypeError: can't send non-None value to a just-started generator
gen.send(None) # ✅ 等价于 next(gen)获取 yield 的返回值
python
def accumulate():
total = 0
while True:
value = yield total # yield 返回当前 total,接收 send 的值
total += value
acc = accumulate()
next(acc) # 启动(yield 0)
print(acc.send(10)) # 10
print(acc.send(20)) # 30
print(acc.send(30)) # 60练习题
练习 1:自定义迭代器
实现一个 Fibonacci 类迭代器,生成前 n 个斐波那契数。
练习 2:生成器平方数
编写生成器函数 squares(n),生成从 1 到 n 的平方数。
练习 3:文件读取生成器
用生成器逐行读取一个文件,过滤掉空行和注释行(以 # 开头)。
参考答案
练习 1:
python
class Fibonacci:
def __init__(self, n):
self.n = n
self.a, self.b = 0, 1
self.count = 0
def __iter__(self):
return self
def __next__(self):
if self.count >= self.n:
raise StopIteration
value = self.a
self.a, self.b = self.b, self.a + self.b
self.count += 1
return value
for f in Fibonacci(10):
print(f, end=" ") # 0 1 1 2 3 5 8 13 21 34练习 2:
python
def squares(n):
for i in range(1, n + 1):
yield i ** 2
print(list(squares(5))) # [1, 4, 9, 16, 25]练习 3:
python
def read_filtered_lines(filename):
with open(filename, "r", encoding="utf-8") as f:
for line in f:
stripped = line.strip()
if stripped and not stripped.startswith("#"):
yield stripped