
yield是 Python 中最容易被低估、但又极其强大的关键字之一。它的核心作用只有一个把一个函数变成“生成器”让函数可以随时“暂停并返回值”下次再从暂停处继续。可以把它理解为函数的“断点保存/恢复”机制。一、yield到底是干什么的yield让函数变成一个“可暂停、可恢复、按需产生值的工厂”。普通函数deff():return1→ 调用一次跑完销毁返回 1。带yield的函数defg():yield1→ 调用g()不执行函数体而是返回一个「生成器对象」→ 每次next(g())函数体向前跑直到遇到yield返回值并暂停→ 下次再next()从yield之后继续。二、最直观示例一步步看发生了什么defcounter():print(start)yield1print(continue)yield2print(end)gencounter()# ⚠️ 函数体没执行print(gen)# generator object counter at ...print(next(gen))# start → 1print(next(gen))# continue → 2print(next(gen))# end → StopIteration关键点✅counter()没有立刻执行✅yield会“返回值 暂停”✅ 最后一次next()抛出StopIterationfor 循环会自动处理三、yield的 6 个核心用途1️⃣ 惰性生成数据最经典用途不用一次性把所有数据放进内存❌ 传统写法内存爆炸defsquares(n):return[i*iforiinrange(n)]# 一次性生成 n 个元素✅ 生成器写法defsquares(n):foriinrange(n):yieldi*iforvinsquares(10_000_000):ifv100:break 优点几乎O(1)内存边生成边消费非常适合大文件 / 大数据 / 流式数据2️⃣ 读取大文件真实生产级用法defread_lines(path,chunk_size1024):withopen(path,r,encodingutf-8)asf:whileTrue:chunkf.read(chunk_size)ifnotchunk:breakyieldchunkforpieceinread_lines(huge.log):process(piece) 这是 Python 处理GB 级日志文件的标准写法。3️⃣ 实现“数据流管道”Unix Pipe 思想defread_file(path):withopen(path)asf:forlineinf:yieldlinedefgrep(lines,keyword):forlineinlines:ifkeywordinline:yieldlinedefcount(lines):cnt0for_inlines:cnt1returncnt linesread_file(access.log)errorsgrep(lines,ERROR)print(count(errors))✅ 特点每一步都是惰性内存占用极低非常像 Linux 的cat access.log | grep ERROR | wc -l4️⃣yield from生成器的“委托调用”definner():yield1yield2defouter():yieldAyieldfrominner()yieldBlist(outer())# [A, 1, 2, B] 作用把内层生成器的产出“透传”出去早期asyncio 协程就是靠yield from实现的后来被async/await取代5️⃣ 协程的前身send()yieldyield不只是“往外吐值”还能“往里收值”defcoro():print(start)xyieldprint(got:,x)ccoro()next(c)# 启动生成器c.send(100)# got: 100 这就是Python 协程的原始形态。后来yieldsend()→asyncio.coroutineasyncio.coroutineyield from→async/await现在用的async/await底层精神就是yield的进化版。6️⃣ 状态机 / 流式解析高级用法deftokenize(text):token[]forchintext:ifch.isalnum():token.append(ch)else:iftoken:yield.join(token)token.clear()iftoken:yield.join(token)list(tokenize(abcdefghi))# [abc, def, ghi]✅ 非常适合词法分析协议解析增量解析 JSON / 日志 / 报文四、yieldvsreturn本质区别特性returnyield是否立即结束✅❌是否可多次产出❌✅是否保存状态❌✅返回值类型任意生成器内存占用高低典型用途计算结果流式数据return 是“我算完了给你结果”yield 是“我先给你一部分我还没结束”。五、yield与async/await的关系非常重要技术作用关系yield暂停函数基础能力yield from嵌套生成器协程前身asyncio.coroutine标记协程历史async/await现代协程语法糖✅async/await ≠ 替代 yield✅async/await 是 yield 的高级封装六、什么时候该用yield✅ 强烈推荐使用读大文件处理流式数据大数据管道自定义迭代逻辑日志/ETL/解析器需要“懒加载”的场景❌ 不适合简单计算需要随机访问逻辑非常简单的小函数已经被async/await覆盖的 IO 场景七、Generator vs Iterator vs Async Generator1️⃣ Iterator迭代器——最底层协议定义实现了__iter__和__next__的对象是 Python 遍历机制的“地基”classMyIter:def__init__(self,n):self.i0self.nndef__iter__(self):returnselfdef__next__(self):ifself.iself.n:raiseStopIteration valself.i self.i1returnvalforiinMyIter(3):print(i)✅ 特点手动维护状态self.i语法啰嗦通用性强是所有“可迭代对象”的底层抽象Iterator 是“能力”不是“写法”2️⃣ Generator生成器——Iterator 的最佳实现方式定义包含yield的函数自动实现__iter____next__自动保存局部状态defmy_gen(n):foriinrange(n):yieldiforiinmy_gen(3):print(i)✅ 特点写法极度简洁状态自动保存惰性计算是 Iterator 的语法糖 最佳实践Generator ≈ 简化版 Iterator3️⃣ Async Generator异步生成器——流式异步数据定义async defyield一边await异步 IO一边产出数据importasyncioasyncdeffetch_pages():foriinrange(3):awaitasyncio.sleep(1)# 模拟异步 IOyieldfpage-{i}asyncdefmain():asyncforpageinfetch_pages():print(page)asyncio.run(main())✅ 特点使用async for可在yield前await适合流式下载 / 消息订阅 / 日志流 / Kafka / 数据库游标Async Generator 异步版 Generator4️⃣ 三者横向对比表重点维度IteratorGeneratorAsync Generator定义方式类 __iter____next__def yieldasync def yield是否自动保存状态❌✅✅是否惰性✅✅✅是否可await❌❌✅遍历方式forforasync for内存占用低极低极低典型场景自定义数据结构大文件 / 数据流异步流数据复杂度高低中八、为什么 async 不用yield了这是一个历史 可读性 语义清晰的问题。1️⃣ 曾经async 就是靠yield活着的在Python 3.4时代asyncio.coroutinedeffetch():yieldfromasyncio.sleep(1)return42asyncio.coroutine只是标记yield from负责“挂起 恢复”asyncio 的事件循环识别yield from来控制协程那时async generator yield from2️⃣ 问题来了yield语义被“绑架”了yield原本的含义是“我是一个生成器我在产出值。”但在 asyncio 里“我是在等 IO我不产出值我只是让出控制权。”这就造成了严重的语义混乱deff():yield1# 是生成值yield2# 还是让出 CPU阅读代码的人根本无法一眼判断。3️⃣ Python 3.5 引入async/await为异步而生的语法asyncdeffetch():awaitasyncio.sleep(1)return42✅ 好处语义分离yield→ 产出数据await→ 等待 IO可读性暴增IDE / 静态分析更友好编译期就能发现错误async/await 是专门为“异步并发”设计的语法糖而不是借用生成器。4️⃣ 那yield在 async 里还有用吗✅有用但用途被严格限定了场景是否用 yield普通 async 函数❌异步 IO 并发❌用 await异步流式数据✅async generator异步迭代器✅async for也就是说async 没“抛弃” yield而是“收回”了它的职责同步世界yield 生成数据异步世界await 等 IO异步流式世界async def yield 异步生成数据5️⃣ 一个对比示例看清本质❌ 旧写法不推荐asyncio.coroutinedefread():responseyieldfromaiohttp.request(...)whileTrue:chunkyieldfromresponse.content.read(1024)ifnotchunk:breakyieldchunk✅ 新写法推荐asyncdefread():asyncwithaiohttp.ClientSession()ass:asyncwiths.get(url)asresp:whileTrue:chunkawaitresp.content.read(1024)ifnotchunk:breakyieldchunk 区别yield from→await语义清晰一看就知道哪里是 IO哪里是产出数据九、终极决策图需要遍历数据 ├─ 是同步数据 │ ├─ 数据量大 / 流式 → Generatoryield │ └─ 简单 / 小数据 → list / iterator ├─ 是异步数据 │ ├─ 只是并发等 IO → async await │ └─ 异步流式产出 → Async Generatorasync yield十、总结yield用于定义生成器使函数在产出值后暂停并保持状态下次调用时从暂停处继续执行。它主要用于惰性计算、流式数据处理和内存优化是 Python 迭代器协议的核心实现方式也是早期协程的基础。Iterator 是协议Generator 是 Iterator 的最佳实现Async Generator 是异步世界的流式产出机制yield曾是 async 的底层支撑但为了让“产出数据”和“等待 IO”语义分离Python 引入了async/await从此await专司异步yield回归数据生成两者各司其职、互不越界。