迭代器与生成器
Iterable 与 Iterator
- Iterable 能通过
iter(obj)产生 Iterator。 - Iterator 通过
next(iterator)逐项返回数据,结束时抛出StopIteration。 - Iterator 通常也是 Iterable,且
iter(iterator) is iterator。
python
values = [1, 2, 3]
iterator = iter(values)
next(iterator) # 1
next(iterator) # 2List 可重复迭代;Iterator 消费后不会自动重置。
生成器
含 yield 的函数调用时返回 Generator,函数体直到第一次 next 才执行:
python
def read_ids(rows):
for row in rows:
yield row["id"]python
ids = (row["id"] for row in rows) # Generator Expression
ids = [row["id"] for row in rows] # 立即创建 List生成器降低峰值内存,但把错误和 IO 推迟到消费阶段。返回生成器的函数如果离开 with open(...) 后才被迭代,文件可能已经关闭;资源生命周期必须覆盖消费过程。
耗尽与隐式消费
以下操作都会消费 Iterator:
python
list(iterator)
sum(iterator)
any(iterator)
all(iterator)
x in iteratorany 和 all 会短路,只消费到能够确定结果的位置。调试时先 list(iterator) 会让正式逻辑看到空数据,是常见问题。
需要同时遍历可用 itertools.tee,但它会缓存两条迭代速度之间的差值;差距很大时可能占用大量内存。
zip 的截断
python
list(zip([1, 2], ["a"])) # [(1, "a")]默认 zip 在最短 Iterable 结束时停止,可能静默丢弃较长输入的尾部。长度必须一致时使用:
python
zip(left, right, strict=True)enumerate
python
for index, value in enumerate(values, start=1):
...不要使用 range(len(values)) 仅为了同时获取索引和值。需要并行修改原 List 时,仍要明确赋值目标与迭代期间结构变化的风险。
迭代时修改容器
遍历 List 时删除元素会移动索引,从而跳过内容:
python
values[:] = [x for x in values if keep(x)]遍历 Dict 或 Set 时改变大小会抛出 RuntimeError。需要修改时遍历快照:
python
for key in list(mapping):
if should_remove(key):
del mapping[key]yield from
yield from iterable 不只是 for 循环缩写,还会在生成器协作中传递 send、throw、close 和子生成器返回值。普通扁平化可以简单理解为委托迭代;实现协程协议时需要了解完整语义。