yield

张开发
2026/6/7 13:03:39 15 分钟阅读
yield
理解yield这个关键字有点像在观察一个平时不太起眼、但关键时刻能派上大用场的小工具。它不是那种一上来就让人眼前一亮的语法比如async/await那样充满未来感而是更内敛、更务实。很多开发者第一次遇到它可能是在处理一个巨大的列表或者是在读一段关于生成器的代码时感觉它有点神秘既不像return那样干脆利落又让函数有了“暂停”和“继续”的能力。其实可以把yield想象成一家生意很好的面包店。通常的面包店普通函数会一次性把所有面包都烤好整盘整盘地摆在橱窗里比如返回一个完整的列表。如果客人不多这没问题。但如果有一天客人源源不断地来面包店就得不停地烤橱窗里可能堆不下或者有些面包还没卖出去就凉了。而用了yield的面包店做法会更巧妙。它有一个小小的窗口后面连着烤箱。客人来一个它就从这个窗口递出一个新鲜出炉、热气腾腾的面包。烤箱里同时还在烤着下一个。客人不用等所有面包都烤好面包店也不用准备巨大的陈列空间。这个“递出一个准备下一个”的节奏就是yield在程序里做的事情。它让函数变成了一个“生成器”每次产生一个值然后停下来等下次需要时再继续。这带来的最大好处是“懒加载”。比如要处理一个有几百万行的日志文件用readlines()一口气全读到内存里电脑可能会很吃力。但如果用一个带了yield的函数一次只读一行处理一行内存的压力就小多了。数据像水流一样按需取用而不是一下子把整个水库都搬过来。另一个有趣的地方是生成器函数在yield停下后它的全部局部状态都会被“冻结”保存下来。等下次再被唤醒时它能从上次离开的地方继续所有变量的值都还是原来的样子。这有点像看书时夹了一张书签下次直接翻到那一页就能接着读不用从头开始找。这种特性使得生成器非常适合用来表示“数据流”或者“状态机”比如按顺序生成ID、遍历复杂的树形结构或者实现一个简单的协程。在底层一个包含yield的函数被调用时返回的并不是一个普通的数值而是一个生成器对象。这个对象有个__next__()方法。每次调用这个方法函数就执行到下一个yield语句吐出值后暂停。直到函数自然结束它会抛出一个StopIteration异常告诉外界“数据已经生成完了。”有时候会看到send()方法和yield配合使用。这就像不仅可以从面包店窗口取面包还能递一张小纸条进去告诉师傅“下一个请少放点糖。” 这让生成器不仅能向外产出数据还能从外部接收数据从而在暂停和恢复之间进行双向通信实现更复杂的控制流。不过这个用法相对小众一些通常出现在需要精细控制生成器的场景里。在实际项目中yield的身影经常出现在处理大规模数据迭代、构建管道化的数据处理流程或者是在一些框架的底层代码中。它可能不会每天都被用到但一旦遇到合适的问题比如内存敏感或者需要惰性求值的场景它往往是最优雅、最高效的解决方案之一。理解它就像是工具箱里多了一件趁手的、专门对付特定问题的工具用对了地方会省力很多。

更多文章