Skip to content

迭代器与生成器 ​

学习目标 ​

  • 区分可迭代对象、迭代器和生成器。
  • 用 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)
# 输出:3

send 会把值送回暂停的 yield 表达式,适合协程式协议;常规业务优先使用普通函数或 async。

易混点 ​

  • 可迭代对象不一定是迭代器;列表可重复遍历,iter(list) 的结果才保存状态。
  • 生成器函数调用不会立即执行函数体。
  • 迭代器耗尽后不能自动复位,想重新开始要重新创建。
  • list(generator) 会一次性加载全部结果,可能失去惰性优势。
  • yield 不是 return:前者暂停并可继续,后者结束函数。

课后小问 ​

  1. 为什么大文件适合逐行迭代而不是 read()? 答案:逐行迭代一次只保留当前片段,内存占用与文件总大小无关。 解析:read() 会把整个文件读进内存;流式消费还能尽早处理结果。

  2. 一个列表能否直接调用 next? 答案:不能,先调用 iter(list) 得到迭代器。 解析:列表是可迭代对象,实现了迭代协议入口,但本身不是保存当前位置的迭代器。

本节小结 ​

先识别“是否可迭代”,再决定是否需要惰性;生成器是写流式逻辑的首选,自定义 __next__ 只在协议确实需要时使用。

快速回顾 ​

  • 协议:iter、next、StopIteration
  • 生成器:yield、yield from
  • 惰性工具:生成器表达式、islice
  • 一次性:迭代器消费后耗尽
  • 协程入口:send