Python 生成器与 yield:一次搞懂惰性求值

举报
yd_255989600 发表于 2026/11/12 06:05:57 2026/11/12
【摘要】 处理大规模数据时,最怕的就是"还没开始算,内存先爆了"。Python 的生成器(Generator)配合 yield 关键字,提供了一种优雅的解决方案:惰性求值(Lazy Evaluation)——数据用到时才生产,用完即弃,内存占用恒定。一、yield 把函数变成工厂普通函数遇到 return 就结束了;而包含 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 流式处理的核心。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。