如果说列表和字典解决的是“怎么存数据”,那么迭代器和生成器解决的就是“怎么流动地处理数据”。它们看起来比普通容器更抽象一些,但一旦理解了惰性计算的好处,就会发现这套机制在性能和表达力上都非常强大。
这一节我们会从 iter() 和 next() 讲起,再过渡到 yield、生成器表达式和实际使用场景,让你真正明白为什么 Python 社区会如此偏爱这类写法。
从可迭代对象开始理解
很多人第一次接触迭代器时,很容易把“可迭代对象”和“迭代器”当成一回事。它们确实很接近,但并不完全相同。
像列表、元组、字符串、字典这些能被 for 遍历的对象,通常都属于可迭代对象。也就是说,它们能够提供一个“把元素一个个拿出来”的能力。
例如:
items = ["python", "fastapi", "sqlalchemy"]
for item in items:
print(item)这里的 items 是可迭代对象,但它本身并不一定就是迭代器。
你可以先建立一个简单心智:
- 可迭代对象负责“提供遍历能力”;
- 迭代器负责“记住当前遍历到哪里”。
这个区别虽然抽象,但一旦想通,你就会更清楚 for 循环背后到底发生了什么。
迭代器协议:iter() 与 next()
Python 的迭代机制建立在一套很简单的协议上。
先通过 iter() 从可迭代对象中拿到迭代器:
items = [1, 2, 3]
iterator = iter(items)然后通过 next() 逐个取值:
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3当元素取完后,再继续调用 next(),就会抛出 StopIteration。
这其实就是 for 循环在底层做的事情。也就是说,for item in items 并不是某种神秘语法糖,而是解释器不断从迭代器里取下一个元素,直到结束。
理解这件事的意义在于:你会意识到 Python 在处理数据流时,并不总是一次性把所有结果都准备好,而是允许它们按需逐步产生。
生成器函数与 yield
生成器是构造迭代器最自然的一种方式。它最显著的特征,就是函数体里出现了 yield。
def count_up_to(limit):
current = 1
while current <= limit:
yield current
current += 1调用这个函数时,它不会像普通函数那样一次性执行到底,而是返回一个生成器对象。每次迭代时,才运行到下一个 yield。
生成器和普通函数的区别
普通函数的心智是“调用一次,执行到底,返回一个结果”。
生成器函数的心智则是“调用后得到一个可迭代的数据流,每次需要时再往前推进一点”。
这意味着生成器特别适合以下场景:
- 结果很多,没必要一次性全部放进内存;
- 数据是逐步产生的;
- 处理链条天然可以按阶段一段段推进。
所以,yield 的本质不是“返回值的另一种写法”,而是把函数从“一次性计算器”变成了“逐步产出结果的迭代器工厂”。
生成器表达式
除了生成器函数,Python 还有生成器表达式:
numbers = (num * 2 for num in range(10))它和列表推导式长得很像,只不过外层用的不是 [],而是 ()。
两者最关键的区别在于:
- 列表推导式会立刻生成完整列表;
- 生成器表达式只会在需要时逐步计算。
这使得生成器表达式在大数据量场景下会更加节省内存,也更适合和 sum()、any()、all() 这类函数配合使用。
惰性计算为什么重要?
所谓惰性计算,可以简单理解成:不急着把所有结果算出来,而是在真正需要时再计算。
这带来的好处非常直接。
第一,是节省内存。如果你要处理一个几百万行的大文件,一次性把所有数据读进列表里,压力会很大;但如果是一行一行地产出、处理、丢弃,内存占用就会稳定很多。
第二,是提升表达能力。很多处理流程天然就是“输入一批,输出一批”,比如过滤、转换、分页拉取、日志流处理。生成器让这类管道式写法变得非常自然。
第三,是提升性能上的灵活性。惰性计算不一定总是更快,但它能避免做“不必要的工作”。如果你只是想找到第一个满足条件的结果,就没必要先把所有候选结果全算完。
所以,惰性计算的核心价值不是“高级”,而是让计算成本和真实需求尽量对齐。
典型应用场景:大文件、流式处理与分页抓取
生成器最典型的使用场景之一,就是大文件处理。
例如:
def read_lines(path):
with open(path, "r", encoding="utf-8") as file:
for line in file:
yield line.strip()这样调用者就可以按行消费,而不是一次性读完整个文件。
另一个很自然的场景,是流式数据处理。例如一批数据先过滤、再转换、再聚合,这时每一步都可以用生成器串成管道。
分页抓取接口时也很常见。你不一定想一次性把所有页面都请求完,而是边请求边处理:
def fetch_all_pages(client):
page = 1
while True:
items = client.fetch(page=page)
if not items:
break
yield from items
page += 1这种写法比“先拉全量列表再统一处理”更贴近真实场景,也更利于控制资源消耗。
总结与预告
这一节我们理解了迭代器与生成器为什么不仅仅是“高级写法”,而是 Python 在处理大规模数据和流式任务时非常关键的一套机制。掌握惰性计算以后,你对内存、性能和表达方式都会有新的感觉。
下一节我们会继续进入 Python 很有代表性的高级特性,从闭包一路走到装饰器,把 @ 背后的本质真正讲明白。