Python 生成器与 yield:一次搞懂惰性求值
处理大规模数据时,最怕的就是"还没开始算,内存先爆了"。Python 的生成器(Generator)配合 yield 关键字,提供了一种优雅的解决方案:惰性求值(Lazy Evaluation)——数据用到时才生产,用完即弃,内存占用恒定。
一、yield 把函数变成工厂
普通函数遇到 return 就结束了;而包含 yield 的函数被调用时并不执行函数体,而是返回一个生成器对象。每次对生成器调用 next(),函数才执行到下一个 yield 处暂停,把值交出去并保留现场(局部变量、指令指针全部冻结),下次调用再从暂停处恢复。
def countdown(n):
while n > 0:
yield n
n -= 1
gen = countdown(3)
print(next(gen)) # 3
print(next(gen)) # 2
for x in gen: # 剩余的 1 会被 for 消费
print(x) # 1
二、为什么省内存
对比 [i*i for i in range(10_000_000)] 与 (i*i for i in range(10_000_000)):前者是列表推导式,立刻构造一千万个元素的列表,占用数百 MB;后者只是生成器表达式,仅仅保存"如何产生下一个值"的规则,内存占用不到 1 KB。遍历文件时用 for line in open('big.log') 而不是 readlines(),同样是这个原理——文件对象本身就是按行迭代的生成器。
三、生成器表达式与管道
生成器可以像流水线一样串联,每条数据流经整条管道,全程无中间大列表:
lines = (l.strip() for l in open('access.log'))
errors = (l for l in lines if 'ERROR' in l)
codes = (l.split(' code=')[1] for l in errors)
四、send 与协程雏形
yield 不仅能"吐"值,还能"收"值:v = yield x 使调用方可以通过 gen.send(value) 向生成器内部传数据,value 就是下次恢复时 yield 表达式的取值。这正是 Python 早期协程(asyncio 前身)的实现基础,也是理解 async/await 的必经之路。
五、使用建议
生成器是一次性的,遍历过就耗尽,需要重复遍历时请用列表;需要"看一遍、过滤一大半"的场景(日志清洗、大文件 ETL、分页拉取 API)最适合生成器。掌握 yield,就掌握了 Python 流式处理的核心。
- 点赞
- 收藏
- 关注作者
评论(0)