← 返回题目列表

Python 的 yield from 是什么?和 for 循环里 yield 有什么区别?

中等 第 18 / 21 题 更新于 2026/07/31
Pythonyield from生成器委托

简化版

**yield from iterable 是「生成器委托」——把一个生成器/可迭代对象的所有值「转发」给外层生成器,等价于 for item in iterable: yield item,但它不只是语法糖:还能自动「打通」子生成器和调用方之间的双向通道(转发 send/throw、接收子生成器的返回值)。**基本用法:yield from sub_gen() 把 sub_gen 产出的所有值逐个 yield 出去,写起来比 for-yield 简洁。它比 for-yield 多做的事:① 转发子生成器的返回值——value = yield from sub() 能拿到子生成器 return 的值(for-yield 拿不到);② 打通双向通信——外层收到的 send(x)throw(e) 会自动转发给子生成器(协程场景关键);③ 自动处理子生成器的关闭/异常最常见的简单用法是「扁平化/委托」:yield from [1,2,3]yield from range(n)、递归展开嵌套结构(yield from flatten(sub))。核心记忆:yield from 把子可迭代对象的值全部转发给外层(≈ for item in it: yield item),但额外转发 send/throw、接收子生成器返回值,是委托子生成器的标准写法。

详细版

yield from vs for-yield

能力for x in it: yield xyield from it
转发所有值
接收子生成器 return 值v = yield from sub()
转发 send/throw 给子✅(协程双向通道)
简洁两行一行
# 基本:转发可迭代对象的所有值
def gen():
    yield from [1, 2, 3]      # 等价 for x in [1,2,3]: yield x
    yield from range(4, 6)    # 4, 5
list(gen())                   # [1, 2, 3, 4, 5]

# 委托子生成器(组合多个生成器)
def sub1():
    yield 'a'; yield 'b'
def sub2():
    yield 'c'; yield 'd'
def combined():
    yield from sub1()
    yield from sub2()
list(combined())              # ['a', 'b', 'c', 'd']

# 递归扁平化嵌套列表(yield from 的经典用途)
def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)   # 递归委托
        else:
            yield item
list(flatten([1, [2, [3, 4], 5], 6]))  # [1, 2, 3, 4, 5, 6]

# 接收子生成器的 return 值(for-yield 做不到)
def sub():
    yield 1
    yield 2
    return "done"             # 子生成器的返回值
def outer():
    result = yield from sub()  # result 拿到 "done"
    print(f"子生成器返回: {result}")
    yield result
list(outer())                 # 打印 "子生成器返回: done",产出 [1, 2, 'done']

# 对比 for-yield:拿不到 return 值
def outer_forloop():
    for x in sub():           # sub 的 return "done" 被丢弃
        yield x

⚠️ 理解要点:yield from 在最简单的场景里就是 for x in it: yield x 的简写(把子可迭代对象的值全部转发出去),但它真正的设计目的(PEP 380)是「让一个生成器能把一部分工作『委托』给子生成器,同时保持双向通道畅通」——这在把生成器当「协程」用时至关重要。当生成器只用来「产出值」时,yield from 和 for-yield 几乎等价(前者更简洁、更快);但当生成器被当协程(用 send() 传值进去、用 throw() 抛异常进去、用 return 返回结果)时,yield from 会自动把外层收到的 send/throw 转发给子生成器、把子生成器 return 的值作为 yield from 表达式的值——这些 for-yield 都做不到,手写转发极其繁琐(PEP 380 的动机就是「消除手写委托的样板代码」)。日常最常用的还是简单场景:扁平化嵌套结构、组合/串联多个生成器、把可迭代对象的值批量 yield。注意 yield from 后面可以跟任何可迭代对象(列表、range、生成器、字符串),不一定是生成器。

完整版教学

一、yield from 是什么

先理解基本语义:

yield from iterable:把 iterable 的所有值转发给外层
  最简单理解 ≈ for item in iterable: yield item

基本用法:
  def gen():
      yield from [1, 2, 3]   # 逐个产出 1, 2, 3
      yield from range(4, 6) # 逐个产出 4, 5
  → 把子可迭代对象"展开"到外层生成器的输出流里

后面可以跟任何可迭代对象:
  yield from [1,2,3]         # 列表
  yield from range(5)        # range
  yield from "abc"           # 字符串(产出 'a','b','c')
  yield from another_gen()   # 生成器
  yield from {1,2,3}         # 集合

对比手写 for-yield:
  # 这两个在"产出值"上等价:
  yield from sub()
  for x in sub(): yield x
  → yield from 更简洁(一行)

为什么叫"委托(delegation)":
  外层生成器把"产出值的工作"委托给子生成器
  外层暂时"退居幕后",子生成器直接和调用方交互
  子生成器耗尽后,控制权回到外层

引入版本:
  Python 3.3(PEP 380)引入 yield from
  动机:简化"生成器委托",尤其是协程场景

所以 yield from 把子可迭代对象所有值转发给外层(≈for x in it:yield x),叫委托

yield from iterable 把 iterable 的所有值转发给外层——最简单理解 ≈ for item in iterable: yield item基本用法yield from [1,2,3] 逐个产出、把子可迭代对象「展开」到外层输出流。后面可以跟任何可迭代对象:列表、range、字符串(产出字符)、生成器、集合。对比手写 for-yieldyield from sub()for x in sub(): yield x 在产出值上等价(yield from 更简洁)。为什么叫「委托(delegation)」:外层生成器把「产出值的工作」委托给子生成器(外层暂时退居幕后、子生成器直接和调用方交互、子耗尽后控制权回外层)。引入版本:Python 3.3(PEP 380),动机是简化生成器委托(尤其协程)。理解「yield from 把子可迭代对象所有值转发给外层(≈for x in it:yield x);后面可跟任何可迭代对象;叫委托(外层把产出工作委托给子生成器);Python 3.3/PEP 380 引入」,就理解了 yield from 是什么。

二、比 for-yield 多做的事

理解 yield from 的额外能力:

yield from 不只是 for-yield 的简写,它还做:

① 转发子生成器的 return 值:
   def sub():
       yield 1
       return "result"      # 子生成器返回
   def outer():
       value = yield from sub()   # value = "result"
       # for-yield 拿不到 return 值(会被丢弃)
   → 子生成器 return 的值成为 yield from 表达式的值

② 转发 send() 给子生成器(双向通道):
   外层收到的 send(x) → 自动转发给正在委托的子生成器
   def sub():
       x = yield        # 接收 send 的值
       yield x * 2
   def outer():
       yield from sub()
   g = outer(); next(g)
   g.send(10)           # 10 被转发给 sub 的 x → 产出 20

③ 转发 throw() 给子生成器:
   外层 throw(异常) → 转发给子生成器处理

④ 自动处理子生成器的 close/StopIteration:
   子生成器结束(StopIteration)→ 自动回到外层
   外层 close → 子生成器也被正确关闭

手写这些转发极其繁琐:
  PEP 380 前,要正确委托一个协程子生成器
  得手写一大段处理 send/throw/return/close 的样板代码
  → yield from 一行搞定所有转发

所以 yield from 额外:转发子生成器 return 值+转发 send/throw+处理 close(for-yield 做不到)

yield from 不只是 for-yield 的简写,它还做① 转发子生成器的 return 值value = yield from sub(),子生成器 return "result" 的值成为 yield from 表达式的值、for-yield 拿不到会被丢弃);② 转发 send() 给子生成器(双向通道)(外层收到的 send(x) 自动转发给正在委托的子生成器);③ 转发 throw() 给子生成器(外层 throw(异常) 转发给子处理);④ 自动处理子生成器的 close/StopIteration(子结束自动回外层、外层 close 时子也被正确关闭)。手写这些转发极其繁琐:PEP 380 前要正确委托一个协程子生成器得手写一大段处理 send/throw/return/close 的样板、yield from 一行搞定。理解「yield from 额外:①转发子生成器 return 值(value=yield from sub())②转发 send 给子(双向通道)③转发 throw④处理 close/StopIteration;for-yield 都做不到;手写委托极繁琐、yield from 一行搞定」,就掌握了额外能力。

三、经典用途:扁平化与组合

理解日常最常用的场景:

日常最常用(简单委托,不涉及协程):

① 扁平化嵌套结构(递归委托):
   def flatten(nested):
       for item in nested:
           if isinstance(item, (list, tuple)):
               yield from flatten(item)   # 递归展开
           else:
               yield item
   list(flatten([1, [2, [3, 4]], 5]))  → [1,2,3,4,5]
   → yield from 让递归生成器写起来自然

② 组合/串联多个生成器:
   def chain_gens():
       yield from gen_a()
       yield from gen_b()
       yield from gen_c()
   → 把多个生成器的输出串成一个流
   (itertools.chain 也能做,但 yield from 更灵活可插逻辑)

③ 批量 yield 一个可迭代对象:
   def with_header():
       yield "HEADER"
       yield from data_rows()   # 批量产出所有行
       yield "FOOTER"

④ 转发子迭代 + 加工:
   def process():
       yield from map(transform, source())
       # 或先产出一部分自己的,再委托
       yield "start"
       yield from items()

⑤ 树遍历(生成器版):
   def inorder(node):
       if node:
           yield from inorder(node.left)
           yield node.val
           yield from inorder(node.right)
   → 中序遍历,惰性产出

对比 itertools.chain:
   yield from a; yield from b  ≈  itertools.chain(a, b)
   → chain 更简洁(纯串联),yield from 能插入自己的逻辑

所以经典用途:扁平化(递归)、组合多生成器、批量 yield、树遍历(生成器版)

日常最常用(简单委托,不涉及协程)① 扁平化嵌套结构(递归委托)yield from flatten(item) 递归展开、让递归生成器写起来自然);② 组合/串联多个生成器yield from gen_a(); yield from gen_b() 把多个生成器输出串成一个流、itertools.chain 也能做但 yield from 更灵活可插逻辑);③ 批量 yield 一个可迭代对象yield "HEADER"; yield from data_rows(); yield "FOOTER");④ 转发子迭代 + 加工⑤ 树遍历(生成器版)yield from inorder(node.left); yield node.val; yield from inorder(node.right) 中序遍历惰性产出)。对比 itertools.chainyield from a; yield from bchain(a, b)(chain 更简洁纯串联、yield from 能插入自己逻辑)。理解「经典用途:①扁平化嵌套(递归 yield from)②组合串联多生成器③批量 yield(带 header/footer)④转发+加工⑤树遍历生成器版;vs itertools.chain(chain 纯串联、yield from 可插逻辑)」,就掌握了经典用途。

四、协程场景(进阶)

理解 yield from 在协程里的作用:

生成器当"协程"用(yield from 的设计初衷):

生成器可以是双向的:
  yield 产出值给调用方
  send(x) 把值传进生成器(yield 表达式的返回值)
  → 生成器变成"能接收也能产出"的协程

协程委托的问题(PEP 380 前):
  外层协程想把一部分工作交给子协程
  但要正确转发 send/throw/return/close 极其麻烦
  → 手写一大段委托样板代码

yield from 解决:一行完成协程委托
  def averager():      # 子协程:累计平均
      total = 0; count = 0
      while True:
          x = yield     # 接收 send 的值
          if x is None: break
          total += x; count += 1
      return total / count   # 返回结果
  def manager():
      result = yield from averager()  # 委托 + 拿返回值
      print(f"平均值: {result}")

  → send 的值自动转发给 averager
  → averager 的 return 值成为 yield from 的值

历史地位:
  yield from(PEP 380, 3.3)是"协程"发展的关键一步
  → 后来 async/await(3.5, PEP 492)取代了用生成器做协程
  → 现在异步编程用 async/await,yield from 主要用于生成器委托
  (async def 里用 await,不用 yield from 做异步)

所以协程场景:yield from 一行完成协程委托(转发 send/throw+拿 return 值),是 async/await 前身

生成器当「协程」用(yield from 的设计初衷):生成器可以是双向的(yield 产出值、send(x) 把值传进生成器成为 yield 表达式的返回值、生成器变成能接收也能产出的协程)。协程委托的问题(PEP 380 前):外层协程想把一部分工作交给子协程、但要正确转发 send/throw/return/close 极其麻烦(手写一大段委托样板)。yield from 解决:一行完成协程委托result = yield from averager()——send 的值自动转发给 averager、averager 的 return 值成为 yield from 的值)。历史地位:yield from(PEP 380、3.3)是协程发展的关键一步、后来 async/await(3.5、PEP 492)取代了用生成器做协程(现在异步编程用 async/await、async def 里用 await 不用 yield from 做异步),yield from 现在主要用于生成器委托。理解「协程场景:生成器双向(yield 产出、send 传入);yield from 一行完成协程委托(转发 send/throw+拿 return 值);是 async/await(3.5)的前身、现在异步用 async/await、yield from 主要用于生成器委托」,就掌握了协程场景。

五、注意事项与陷阱

理解 yield from 的注意点:

注意事项:

① yield from 后面可以是任何可迭代对象:
   yield from [1,2,3]      # 列表
   yield from "abc"        # 字符串 → 'a','b','c'
   yield from range(5)
   → 不限于生成器

② 空可迭代对象:yield from [] 什么都不产出(正常)

③ return 值只在"委托给生成器"时有意义:
   value = yield from gen()   # gen 的 return 值
   value = yield from [1,2,3] # 列表没有 return,value 是 None
   → 只有生成器能 return 值

④ 别和普通 yield 混淆:
   yield gen()        # 产出"生成器对象本身"(一个值)
   yield from gen()   # 产出生成器的"所有值"(展开)
   → 差一个 from,语义完全不同!

⑤ 异常传播:
   子生成器内的异常会传播到外层
   外层的 throw 会转发给子生成器

⑥ 性能:
   yield from 比手写 for-yield 略快(C 层优化转发)

⑦ 不要在 async def 里用 yield from:
   async def 用 await(不是 yield from)
   → yield from 是生成器委托、await 是异步等待,别混

常见错误:
   yield gen()  想展开但只产出了生成器对象
   → 要展开用 yield from gen()

所以注意:后面可跟任何可迭代、return 值只对生成器有意义、别和 yield gen()混淆

注意事项① yield from 后面可以是任何可迭代对象(列表、字符串、range,不限于生成器);② 空可迭代对象yield from [] 什么都不产出);③ return 值只在委托给生成器时有意义yield from [1,2,3] 列表没有 return、value 是 None);④ 别和普通 yield 混淆yield gen() 产出生成器对象本身、yield from gen() 产出生成器的所有值展开,差一个 from 语义完全不同);⑤ 异常传播(子生成器异常传到外层、外层 throw 转发给子);⑥ 性能(yield from 比手写 for-yield 略快);⑦ 不要在 async def 里用 yield from(async def 用 await、yield from 是生成器委托、await 是异步等待)。理解「注意:①后面可跟任何可迭代②空的不产出③return 值只对生成器有意义④别和 yield gen()混淆(差一个 from、语义全不同)⑤异常传播⑥略快⑦async def 用 await 不用 yield from」,就掌握了注意事项。

六、总结与实践

总结 yield from:

核心:
  yield from iterable:把可迭代对象的所有值转发给外层
  最简单 ≈ for x in it: yield x(但不止于此)

比 for-yield 多做:
  ① 接收子生成器的 return 值(value = yield from gen())
  ② 转发 send/throw 给子生成器(协程双向通道)
  ③ 自动处理子生成器的 close/异常

日常最常用(简单委托):
  扁平化嵌套(递归 yield from)
  组合/串联多个生成器
  批量 yield 可迭代对象
  树遍历(生成器版)

进阶(协程):
  yield from 一行完成协程委托(是 async/await 前身)
  现在异步用 async/await,yield from 主要用于生成器委托

易错:
  yield gen()(产出生成器对象) vs yield from gen()(展开)
  差一个 from,语义完全不同

实践建议:
  批量转发可迭代对象 → yield from(简洁、快)
  纯串联生成器 → itertools.chain 也可
  异步编程 → async/await(不是 yield from)

核心总结:
  yield from 委托子可迭代对象(转发所有值)
  额外转发 send/throw、接收 return 值
  日常用于扁平化/组合/树遍历

所以 yield from 委托子可迭代(转发所有值+send/throw+return),日常扁平化/组合

核心yield from iterable 把可迭代对象的所有值转发给外层(最简单 ≈ for x in it: yield x 但不止于此)。比 for-yield 多做:① 接收子生成器的 return 值、② 转发 send/throw 给子(协程双向通道)、③ 自动处理 close/异常。日常最常用:扁平化嵌套、组合串联多生成器、批量 yield、树遍历。进阶(协程):一行完成协程委托(是 async/await 前身、现在异步用 async/await)。易错yield gen()(产出生成器对象)vs yield from gen()(展开)差一个 from。理解「yield from 委托子可迭代(转发所有值);额外转发 send/throw+接收 return 值;日常扁平化/组合/树遍历;易错 yield gen() vs yield from gen();异步用 async/await」,就掌握了总结与实践。

记忆钩子:「yield from iterable 是『生成器委托』——把子生成器/可迭代对象的所有值转发给外层生成器,最简单理解≈for item in iterable:yield item(把子可迭代对象的值全部展开到外层输出流);★但它不只是语法糖(PEP 380/Python 3.3),比 for-yield 多做三件事:①接收子生成器的 return 值(value=yield from sub(),for-yield 会丢弃 return 值)②转发 send()/throw()给正在委托的子生成器(打通协程双向通道)③自动处理子生成器的 close/StopIteration;★日常最常用是简单委托:扁平化嵌套结构(递归 yield from flatten(item))、组合串联多个生成器(yield from a;yield from b≈itertools.chain 但可插逻辑)、批量 yield 可迭代对象、树遍历生成器版(yield from inorder(left);yield val;yield from inorder(right));★易错:yield gen()产出『生成器对象本身』(一个值)、yield from gen()产出生成器的『所有值』(展开),差一个 from 语义完全不同;后面可跟任何可迭代对象(列表/range/字符串/生成器);★协程场景是它的设计初衷(是 async/await 的前身),但现在异步编程用 async/await(async def 里用 await 不用 yield from),yield from 主要用于生成器委托」

七、常见误区与追问

  • 误区:yield from it 和 for x in it: yield x 完全等价。 在「只产出值」时等价(也更简洁、略快),但 yield from 还多做三件 for-yield 做不到的事:① 能接收子生成器 return 的值(value = yield from gen(),for-yield 会丢弃 return 值);② 把外层收到的 send()/throw() 自动转发给正在委托的子生成器(打通协程双向通道);③ 自动处理子生成器的关闭和异常;所以在把生成器当协程用时两者有本质区别。
  • 误区:yield gen() 和 yield from gen() 一样。 差一个 from、语义完全不同——yield gen() 是把「生成器对象本身」作为一个值产出(外层只 yield 出一个生成器对象),yield from gen() 是把「生成器产出的所有值」逐个转发出去(展开);比如 def f(): yield gen() 迭代得到 [<generator object>]def f(): yield from gen() 迭代得到生成器的实际内容;想「展开」子生成器一定要用 yield from
  • 误区:yield from 后面必须是生成器。 不是——yield from 后面可以跟任何可迭代对象:列表 yield from [1,2,3]、range yield from range(5)、字符串 yield from "abc"(产出 ‘a’,‘b’,‘c’)、集合、其他生成器都行;只不过「接收 return 值」这个特性只有子对象是生成器时才有意义(列表等没有 return、value = yield from [1,2,3] 里 value 是 None)。
  • 误区:在 async def 里用 yield from 做异步等待。 不对——yield from 是「生成器委托」、用在普通生成器函数里;异步函数(async def)里要用 await 来等待协程/可等待对象;虽然历史上 asyncio 早期确实用过 yield from 配合生成器协程(@asyncio.coroutine + yield from),但那是 Python 3.5 之前的旧写法,现在一律用 async def + awaityield fromawait 是两套东西,别在异步代码里混用。
  • 追问:yield from 相比手写 for-yield,多了哪些能力,为什么需要它? 多了三个关键能力:① 接收子生成器的返回值——value = yield from sub() 能拿到 sub 里 return x 的 x(for-yield 里子生成器的 return 值会被直接丢弃);② 双向通道转发——外层生成器收到的 send(值)throw(异常)close() 会自动转发给当前正在委托的子生成器,让调用方能「穿透」外层直接和子生成器交互;③ 自动处理子生成器的 StopIteration/异常/关闭;PEP 380 引入它的动机就是:在把生成器当协程用时,手写这些转发逻辑(正确处理 send/throw/return/close 的各种情况)需要一大段繁琐易错的样板代码,yield from 把这些全部封装成一行,让「生成器委托子生成器」变得简单可靠。
  • 追问:yield from 在实际代码里最常见的用途是什么? 最常见的是「简单委托」(不涉及协程的双向通信):① 递归扁平化嵌套结构——def flatten(nested): for x in nested: yield from flatten(x) if isinstance(x, list) else [x],让递归生成器写起来自然;② 组合/串联多个生成器成一个输出流——def combined(): yield from gen_a(); yield from gen_b()(类似 itertools.chain,但可以在中间插入自己的逻辑或额外的 yield);③ 批量产出一个可迭代对象的所有值——yield "开始"; yield from data(); yield "结束";④ 生成器版的树遍历——中序遍历 yield from inorder(left); yield val; yield from inorder(right),惰性地按顺序产出节点;这些场景里 yield from 主要图的是「简洁 + 略快 + 递归自然」。
  • 追问:yield from 和 async/await 是什么关系? yield from(PEP 380,Python 3.3)是「生成器委托」机制,也是 Python 协程发展史上的关键一步——在 async/await 出现之前,asyncio 就是用「生成器 + yield from」来实现协程和协程委托的(@asyncio.coroutine 装饰的生成器里用 yield from 等待另一个协程);后来 Python 3.5(PEP 492)引入了专门的 async defawait 语法,把「异步协程」和「普通生成器」在语法上彻底分开,await 承担了当年 yield from 在异步场景的角色、语义更清晰;所以可以说 yield from 是 async/await 的「前身/思想来源」;现在的分工是:异步编程用 async def + await,而 yield from 回归到它最基础的用途——普通生成器之间的委托(扁平化、组合、树遍历等),两者不要混用。

八、加强记忆

yield from iterable 是「生成器委托」——把子生成器/可迭代对象的所有值转发给外层生成器,最简单理解 ≈ for item in iterable: yield item(把子可迭代对象的值全部展开到外层输出流)。但它不只是语法糖(PEP 380/Python 3.3),比 for-yield 多做三件事:① 接收子生成器的 return 值value = yield from sub(),for-yield 会丢弃 return 值)、② 转发 send()/throw() 给正在委托的子生成器(打通协程双向通道)、③ 自动处理子生成器的 close/StopIteration日常最常用是简单委托:扁平化嵌套结构(递归 yield from flatten(item))、组合串联多个生成器(yield from a; yield from b ≈ itertools.chain 但可插逻辑)、批量 yield 可迭代对象、树遍历生成器版。易错yield gen() 产出「生成器对象本身」(一个值)、yield from gen() 产出生成器的「所有值」(展开),差一个 from 语义完全不同;后面可跟任何可迭代对象。协程场景是它的设计初衷(是 async/await 的前身),但现在异步编程用 async/await(async def 里用 await 不用 yield from),yield from 主要用于生成器委托。一句话「yield from 委托子可迭代对象、转发其所有值(≈for x in it:yield x);额外接收子生成器 return 值、转发 send/throw、处理 close;日常用于扁平化/组合生成器/树遍历;别和 yield gen()混淆(差一个 from);异步用 async/await 不是 yield from」。