迭代器与生成器
学习目标
- 区分可迭代对象、迭代器和生成器。
- 用
iter、next和yield处理流式数据。 - 在惰性求值、内存占用和可重复遍历之间做选择。
核心知识点
可迭代对象能被 iter() 转成迭代器;迭代器保存当前位置并通过 __next__() 逐个产生值,耗尽后抛 StopIteration。生成器函数含 yield,调用时返回生成器,执行会在每次 yield 处暂停。
常用用法
iter() 与 next()
python
values = iter([10, 20])
first = next(values)
second = next(values)
print(first, second)
# 输出:10 20迭代器是一次性的;需要安全读取下一个值时可传默认值给 next(iterator, default)。
for 消费可迭代对象
python
values = (number * 2 for number in range(3))
result = list(values)
print(result)
# 输出:[0, 2, 4]for 自动调用 iter 和 next 并处理 StopIteration,日常代码不必手写协议。
生成器函数 yield
python
def countdown(start: int):
while start:
yield start
start -= 1
print(list(countdown(3)))
# 输出:[3, 2, 1]生成器只在消费时计算值,适合大数据或无限序列。
yield from
python
def combined():
yield from [1, 2]
yield from (3, 4)
print(list(combined()))
# 输出:[1, 2, 3, 4]yield from 把子迭代器的值逐个转发,适合组合生成器。
生成器表达式
python
numbers = (number * number for number in range(4))
total = sum(numbers)
print(total)
# 输出:14圆括号表达式是惰性的;若需要多次遍历或索引,直接构造列表。
enumerate 与 zip 的惰性
python
names = ["Ada", "Lin"]
scores = [98, 91]
rows = list(enumerate(zip(names, scores), start=1))
print(rows)
# 输出:[(1, ('Ada', 98)), (2, ('Lin', 91))]这些内置函数返回迭代器,明确在边界 list(...) 化便于调试和复用。
自定义迭代器
python
class CountUp:
def __init__(self, stop: int) -> None:
self.current = 0
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += 1
return value
print(list(CountUp(3)))
# 输出:[0, 1, 2]自定义迭代器必须实现 __iter__ 和 __next__;大多数场景用生成器更简单。
不常用但需要知道
next(iterator, default)
python
values = iter([1])
print(next(values, None), next(values, None))
# 输出:1 None默认值只在耗尽时返回,不会区分“真实值就是 None”的情况,需要时使用哨兵对象。
itertools.islice()
python
from itertools import islice
values = (number * number for number in range(10))
print(list(islice(values, 2, 6, 2)))
# 输出:[4, 16]islice 对惰性流做切片,不会像列表切片那样随机访问。
generator.send()
python
def accumulator():
total = 0
while True:
value = yield total
total += value
stream = accumulator()
next(stream)
current = stream.send(3)
print(current)
# 输出:3send 会把值送回暂停的 yield 表达式,适合协程式协议;常规业务优先使用普通函数或 async。
易混点
- 可迭代对象不一定是迭代器;列表可重复遍历,
iter(list)的结果才保存状态。 - 生成器函数调用不会立即执行函数体。
- 迭代器耗尽后不能自动复位,想重新开始要重新创建。
list(generator)会一次性加载全部结果,可能失去惰性优势。yield不是return:前者暂停并可继续,后者结束函数。
课后小问
为什么大文件适合逐行迭代而不是
read()? 答案:逐行迭代一次只保留当前片段,内存占用与文件总大小无关。 解析:read()会把整个文件读进内存;流式消费还能尽早处理结果。一个列表能否直接调用
next? 答案:不能,先调用iter(list)得到迭代器。 解析:列表是可迭代对象,实现了迭代协议入口,但本身不是保存当前位置的迭代器。
本节小结
先识别“是否可迭代”,再决定是否需要惰性;生成器是写流式逻辑的首选,自定义 __next__ 只在协议确实需要时使用。
快速回顾
- 协议:
iter、next、StopIteration - 生成器:
yield、yield from - 惰性工具:生成器表达式、
islice - 一次性:迭代器消费后耗尽
- 协程入口:
send
阿源的知识库