欢迎光临
我们一直在努力

《流畅的Python》读书笔记19: 第四部分 控制流 - 使用 yield from

作者: andylin02 学习章节: 第 18 章 使用 yield from 关键词: yield from|委托生成器|子生成器|双向通道|PEP 380|StopIteration|异常传播|asyncio|协程委派


一、本章概述

第 18 章“使用 yield from”是《流畅的 Python》第二版中基于生成器的协程(classic coroutine)部分的终点,同时也是通向现代 async/await 异步编程的一座关键桥梁。

在 Python 3.3 之前,当我们想要将一个生成器的产出“转发”给另一个生成器时,必须手动编写 for 循环来迭代子生成器并逐一 yield 其产出的值。然而,当一个生成器还需要处理 .send()、.throw() 和 .close() 方法时,纯手工转发的代码就会变得异常复杂且极易出错。

yield from 正是为解决这个问题而生的语言结构。正如 PEP 380 中所述:Python 生成器是一种协程形式,但它有一个限制,即它只能向其直接调用者让出。yield from 解决了这个限制,它允许一个生成器将其部分操作委托给另一个生成器,使子生成器的产出的值可以直接传给调用方,调用方通过 .send() 发送的值也可以直接传进子生成器。与此同时,它还能透明地传播异常,并捕获子生成器的返回值供委派生成器使用。

本章将以 yield from 的“双向通道”(bidirectional tunnel)机制为核心,逐步展开讲解:

  • 从手动委托到自动委托:理解 yield from 如何简化生成器之间的值转发。
  • 双向通道的建立:.send()、.throw() 和 .close() 如何在调用方、委托生成器和子生成器之间透明传递。
  • 子生成器的返回值:StopIteration 异常的妙用——子生成器的 return 语句如何将值带给 yield from 表达式。
  • 异常处理:yield from 如何处理子生成器抛出的异常,以及如何通过抛出 GeneratorExit 来关闭子生成器。
  • 综合案例:通过一个实例展示委派生成器如何收集子生成器的返回值。
  • 与现代 async/await 的对比:理解 yield from 与 await 的本质联系。

“yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因此,x 可以是任何可迭代的对象。可是,如果 yield from 结构唯一的作用是替代产出值的嵌套 for 循环,这个结构很有可能不会添加到 Python 语言中。yield from 结构的本质作用无法通过简单的可迭代对象说明,而要发散思维,使用嵌套的生成器。”

二、yield from 的两种使用层次

要全面理解 yield from,需要从两个层次来看待它:

  • 简化 for 循环:当 yield from 的对象是一个普通的可迭代对象时,它等价于一个简单的嵌套 for 循环,用于“扁平化”地依次产出多个可迭代对象中的所有元素。这是最简单的理解,也是最直观的用法。

  • 建立双向通道:当 yield from 的对象是一个生成器时,yield from 的真正威力在于建立起调用方、委托生成器和子生成器之间的透明双向通道——这是 PEP 380 的核心价值。

  • 接下来的章节,我们将从简单层次开始,逐步深入到复杂层次。

    2.1 层次一:简化 for 循环——扁平化多个可迭代对象

    yield from 最基本的用法是替代嵌套 for 循环,将多个可迭代对象依次产出元素:

    def chain(*iterables):
    for it in iterables:
    for item in it:
    yield item

    上述写法可以简化为:

    def chain(*iterables):
    for it in iterables:
    yield from it

    s = 'ABC'
    t = tuple(range(3))
    print(list(chain(s, t)))

    运行结果:

    ['A', 'B', 'C', 0, 1, 2]

    等价关系:yield from <iterable> 等价于 for item in <iterable>: yield item,但这种等价关系仅适用于最简单的情形——不涉及 .send() 和 .throw() 时。当被委托的对象是一个生成器并且需要双向通信时,yield from 远不止语法糖这么简单。

    三、层次二:双向通道——yield from 的核心机制

    当 yield from 后面的对象是一个生成器时,yield from 不只是简化了嵌套循环,它在调用方和被委托的子生成器之间建立了一条透明的双向通道——这正是 PEP 380 所定义的委托生成器(delegating generator)和子生成器(subgenerator)。

    PEP 380 用了一整个章节的篇幅来形式化描述 yield from 的语义。如果只用一个词来概括这些复杂形式语义的精髓,那就是“双向通道”——yield from 创建一个透明双向通道,把最外层的调用方与最内层的子生成器连接起来,使二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的委派生成器中添加大量处理异常的样板代码。

    在深入理解双向通道之前,先来明确这三个角色的定义:

    角色说明
    调用方(Caller) 驱动协程的客户端代码,调用 next()、.send()、.throw() 等方法
    委托生成器(Delegating Generator) 包含 yield from subgen() 表达式的生成器函数
    子生成器(Subgenerator) 从 yield from <expr> 中的 <expr> 部分获取的生成器

    明确这三个角色后,我们通过一个具体示例来观察 yield from 双向通道的三个阶段:

    def delegator():
    print("[DELEGATOR] Starting")
    result = yield from subgenerator()
    print(f"[DELEGATOR] Subgen returned: {result}")
    yield "done"

    def subgenerator():
    print("[SUBGEN] Starting")
    yield "first"
    yield "second"
    print("[SUBGEN] Exhausted")
    return "FINAL_VALUE"

    gen = delegator()
    print(next(gen)) # "first" — 来自子生成器
    print(next(gen)) # "second" — 来自子生成器
    print(next(gen)) # "done" — 委派生成器在子生成器耗尽后恢复

    运行结果:

    [DELEGATOR] Starting
    [SUBGEN] Starting
    first
    second
    [SUBGEN] Exhausted
    [DELEGATOR] Subgen returned: FINAL_VALUE
    done

    这个例子清晰地展示了 yield from 的三个阶段:

    • 阶段一:委托启动 → delegator() 运行直到遇到 yield from subgenerator(),控制权完全转移到 subgenerator(),委托生成器的栈帧被冻结。
    • 阶段二:透明代理 → 每次对 gen 调用 next(),值都直接从 subgenerator() 流向调用方,委托生成器在此期间完全不唤醒。
    • 阶段三:耗尽与返回值捕获 → 当 subgenerator() 引发 StopIteration 异常时,Python 捕获该异常,将其中的值赋给 result,然后委托生成器恢复执行。

    3.1 双向通道:send/throw/close 的透明传递

    yield from 建立的不仅仅是值的单向流动——它是真正的双向通道。调用方通过 .send() 发送的值直接传给了子生成器;yield from 表达式本身的值则是子生成器终止时抛出的 StopIteration 异常中携带的值。

    下面用完整的双向通信示例来展示这一机制(扩展自书中“averager”子生成器案例):

    from collections import namedtuple

    Result = namedtuple('Result', 'count average')

    def averager():
    """子生成器——计算平均值并返回统计结果"""
    total = 0.0
    count = 0
    while True:
    term = yield
    if term is None:
    break
    total += term
    count += 1
    return Result(count, total / count) if count else Result(0, 0)

    def grouper(results, key):
    """委派生成器——为每个键收集数据并委托给 averager"""
    while True:
    results[key] = yield from averager()

    def main(data):
    results = {}
    for key, values in data.items():
    group = grouper(results, key)
    next(group) # 预激委派生成器
    for value in values:
    group.send(value)
    group.send(None) # 终止子生成器,触发返回值捕获

    data = {
    'girls;kg': [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m': [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
    }

    if __name__ == '__main__':
    main(data)
    for key, result in results.items():
    print(f'{key}: {result}')

    yiled from 数据流架构图

    #mermaid-svg-ZiYdB4aTCKIGZAbt{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZiYdB4aTCKIGZAbt .error-icon{fill:#552222;}#mermaid-svg-ZiYdB4aTCKIGZAbt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZiYdB4aTCKIGZAbt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .marker.cross{stroke:#333333;}#mermaid-svg-ZiYdB4aTCKIGZAbt svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZiYdB4aTCKIGZAbt p{margin:0;}#mermaid-svg-ZiYdB4aTCKIGZAbt .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .cluster-label text{fill:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .cluster-label span{color:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .cluster-label span p{background-color:transparent;}#mermaid-svg-ZiYdB4aTCKIGZAbt .label text,#mermaid-svg-ZiYdB4aTCKIGZAbt span{fill:#333;color:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .node rect,#mermaid-svg-ZiYdB4aTCKIGZAbt .node circle,#mermaid-svg-ZiYdB4aTCKIGZAbt .node ellipse,#mermaid-svg-ZiYdB4aTCKIGZAbt .node polygon,#mermaid-svg-ZiYdB4aTCKIGZAbt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .rough-node .label text,#mermaid-svg-ZiYdB4aTCKIGZAbt .node .label text,#mermaid-svg-ZiYdB4aTCKIGZAbt .image-shape .label,#mermaid-svg-ZiYdB4aTCKIGZAbt .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZiYdB4aTCKIGZAbt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .rough-node .label,#mermaid-svg-ZiYdB4aTCKIGZAbt .node .label,#mermaid-svg-ZiYdB4aTCKIGZAbt .image-shape .label,#mermaid-svg-ZiYdB4aTCKIGZAbt .icon-shape .label{text-align:center;}#mermaid-svg-ZiYdB4aTCKIGZAbt .node.clickable{cursor:pointer;}#mermaid-svg-ZiYdB4aTCKIGZAbt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .arrowheadPath{fill:#333333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZiYdB4aTCKIGZAbt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZiYdB4aTCKIGZAbt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZiYdB4aTCKIGZAbt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZiYdB4aTCKIGZAbt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .cluster text{fill:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt .cluster span{color:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZiYdB4aTCKIGZAbt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZiYdB4aTCKIGZAbt rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZiYdB4aTCKIGZAbt .icon-shape,#mermaid-svg-ZiYdB4aTCKIGZAbt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZiYdB4aTCKIGZAbt .icon-shape p,#mermaid-svg-ZiYdB4aTCKIGZAbt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZiYdB4aTCKIGZAbt .icon-shape .label rect,#mermaid-svg-ZiYdB4aTCKIGZAbt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZiYdB4aTCKIGZAbt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZiYdB4aTCKIGZAbt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZiYdB4aTCKIGZAbt :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    子生成器 (averager)

    委派生成器 (grouper)

    调用方 (main)

    值/产出

    透明转发

    产出值

    透明转发

    发送 None

    转发

    StopIteration(Result)

    赋值给 results[key]

    .send(value)

    .send(None)

    yield from averager()

    term = yield

    计算结果

    return Result

    此例完整地展现了 yield from 的三个关键能力:

  • 值的双向传递:调用方通过 group.send(value) 发送的值被 yield from 透明转发给子生成器,子生成器通过 term = yield 接收。
  • 子生成器的返回值:子生成器的 return 语句会触发 StopIteration 异常,其中携带的返回值被 yield from 表达式捕获,成为 results[key] = yield from averager() 的右值。如果子生成器没有返回显式值,则 yield from 表达式的值为 None。
  • 委托生成器的无限循环:由于 grouper 内部是 while True 循环,每次子生成器终止后,它会立即重新创建一个新的 averager 实例,继续处理下一个数据组。这在处理分组数据时极为高效:同一个委派生成器实例可以被重复使用,持续处理多组数据。
  • 四、通过 throw 和 close 在协程中管理异常和生命周期

    了解如何优雅地向一个活跃的协程中注入异常、以及如何正确地关闭协程非常重要。

    4.1 异常的透明传播

    当委托生成器收到调用方通过 .throw() 方法抛出的异常时,它会将该异常透明地传递给子生成器:

    class DemoException(Exception):
    pass

    def subgen():
    while True:
    try:
    val = yield
    print(f"Received: {val}")
    except DemoException:
    print("DemoException handled, continue")

    def delegator():
    yield from subgen()

    coro = delegator()
    next(coro) # 预激

    coro.send(10) # Received: 10
    coro.throw(DemoException) # DemoException handled, continue
    coro.send(20) # Received: 20
    coro.close() # 正常关闭

    如果子生成器捕获并处理了异常,协程会继续运行;如果子生成器没有捕获它,该异常会向上传播并终止整个链。

    4.2 close() 与 GeneratorExit

    调用 .close() 方法会在协程当前挂起的 yield 表达式处抛出 GeneratorExit 异常。子生成器收到该异常后应执行清理操作并终止。

    def subgen():
    try:
    while True:
    val = yield
    except GeneratorExit:
    print("Subgenerator cleaning up…")
    raise # 必须重新抛出

    def delegator():
    yield from subgen()
    print("Delegator done")

    coro = delegator()
    next(coro)
    coro.close() # Subgenerator cleaning up…

    协程在关闭时必须重新抛出 GeneratorExit,否则 Python 运行时会产生 RuntimeError。

    五、基于生成器的协程与 @asyncio.coroutine

    在 Python 3.4 中,asyncio 库正式加入标准库。当时异步协程的实现方式就是使用 @asyncio.coroutine 装饰器和 yield from 语法来等待期物(future)或其他协程。

    典型写法(仅作对比,现代 Python 已不再使用):

    import asyncio

    @asyncio.coroutine
    def fetch_data():
    result = yield from some_async_operation()
    return result

    这与 yield from subgenerator() 的本质逻辑是一致的:都利用了 yield from 建立的双向通道将底层生成器(或期物)的产出值和传入值透明地连接到最外层的调用方。

    5.1 yield from 与 await 的异同

    “yield from 是一种等待 asyncio 协程的旧方法。await 是一种等待 asyncio 协程的现代方式。”

    重要区别:

    • yield from 可以用在常规的生成器函数中,而 await 只能用在 async def 声明的原生协程函数中。
    • yield from 可用于任何可迭代对象;而 await 只接受 Awaitable 对象(如协程对象、asyncio.Future 或实现了 __await__ 方法的对象)。
    • async/await 牺牲了 yield from 的通用性,换来的是更低的认知成本和更高的工程可靠性。

    技术层面:await 做的事情与 yield from 完全相同。但 await 只能用在 async def 函数中,配合更清晰的语法,使得异步代码更加直观易读。

    六、本章思维导图

    #mermaid-svg-mLzU994XxC9Y1oiZ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mLzU994XxC9Y1oiZ .error-icon{fill:#552222;}#mermaid-svg-mLzU994XxC9Y1oiZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mLzU994XxC9Y1oiZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mLzU994XxC9Y1oiZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mLzU994XxC9Y1oiZ .marker.cross{stroke:#333333;}#mermaid-svg-mLzU994XxC9Y1oiZ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mLzU994XxC9Y1oiZ p{margin:0;}#mermaid-svg-mLzU994XxC9Y1oiZ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ .cluster-label text{fill:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ .cluster-label span{color:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ .cluster-label span p{background-color:transparent;}#mermaid-svg-mLzU994XxC9Y1oiZ .label text,#mermaid-svg-mLzU994XxC9Y1oiZ span{fill:#333;color:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ .node rect,#mermaid-svg-mLzU994XxC9Y1oiZ .node circle,#mermaid-svg-mLzU994XxC9Y1oiZ .node ellipse,#mermaid-svg-mLzU994XxC9Y1oiZ .node polygon,#mermaid-svg-mLzU994XxC9Y1oiZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mLzU994XxC9Y1oiZ .rough-node .label text,#mermaid-svg-mLzU994XxC9Y1oiZ .node .label text,#mermaid-svg-mLzU994XxC9Y1oiZ .image-shape .label,#mermaid-svg-mLzU994XxC9Y1oiZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-mLzU994XxC9Y1oiZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mLzU994XxC9Y1oiZ .rough-node .label,#mermaid-svg-mLzU994XxC9Y1oiZ .node .label,#mermaid-svg-mLzU994XxC9Y1oiZ .image-shape .label,#mermaid-svg-mLzU994XxC9Y1oiZ .icon-shape .label{text-align:center;}#mermaid-svg-mLzU994XxC9Y1oiZ .node.clickable{cursor:pointer;}#mermaid-svg-mLzU994XxC9Y1oiZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mLzU994XxC9Y1oiZ .arrowheadPath{fill:#333333;}#mermaid-svg-mLzU994XxC9Y1oiZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mLzU994XxC9Y1oiZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mLzU994XxC9Y1oiZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mLzU994XxC9Y1oiZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mLzU994XxC9Y1oiZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mLzU994XxC9Y1oiZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mLzU994XxC9Y1oiZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mLzU994XxC9Y1oiZ .cluster text{fill:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ .cluster span{color:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mLzU994XxC9Y1oiZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mLzU994XxC9Y1oiZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-mLzU994XxC9Y1oiZ .icon-shape,#mermaid-svg-mLzU994XxC9Y1oiZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mLzU994XxC9Y1oiZ .icon-shape p,#mermaid-svg-mLzU994XxC9Y1oiZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mLzU994XxC9Y1oiZ .icon-shape .label rect,#mermaid-svg-mLzU994XxC9Y1oiZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mLzU994XxC9Y1oiZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mLzU994XxC9Y1oiZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mLzU994XxC9Y1oiZ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    第18章 使用 yield from

    起源:PEP 380

    问题:生成器只能Yield给直接调用者

    委托给子生成器

    两种使用层次

    层次一:简化for循环

    扁平化多个可迭代对象

    层次二:双向通道

    透明连接调用方与子生成器

    三方角色

    调用方 – 驱动协程

    委托生成器 – 含yield from

    子生成器 – 实际干活者

    核心机制

    值透明转发: next/send

    子生成器返回值: StopIteration

    异常透明传播: throw

    关闭: close/GeneratorExit

    yield from vs await

    yield from: 老式等待协程

    await: 现代等待协程

    async/await 代码更直观

    七、常见错误与最佳实践

    错误原因解决方案
    忘记预激协程 协程在 GEN_CREATED 状态直接调用 .send() 先调用 next(coro) 或 coro.send(None)
    未捕获子生成器的返回值 子生成器的 return 值会隐藏在 StopIteration 中 使用 yield from 表达式捕获该值
    误用 yield from None None 不可迭代,会触发 TypeError 确保 yield from 后的表达式可迭代
    在协程中捕获 StopIteration 自 Python 3.5+,StopIteration 在协程中会被转换为 RuntimeError 使用 yield from 表达式获取返回值,不要手动捕获
    .close() 后忘记重新抛出 GeneratorExit 不抛出会导致 RuntimeError 在 finally 块或 except GeneratorExit 中调用 raise

    最佳实践:

    • 使用 yield from 代替手写 for 循环来简洁地表达生成器委托。
    • 当子生成器需要返回计算结果时,使用 return value 语句,并通过 yield from 表达式捕获返回值。
    • 在委派生成器中,通常配合 while True + yield from 来实现对多组数据的持续处理,这样同一个委派生成器实例可以被重复使用。
    • 区别对待显式 return 与自然结束:在生成器中 return 会触发 StopIteration 并携带返回值。
    • 在现代 Python 项目中,编写新的异步代码时优先使用 async/await 语法而非 @asyncio.coroutine + yield from,除非维护旧版代码。

    八、本章总结

    第 18 章“使用 yield from”是对委托生成器与协程之间协作方式的完整论述。经过学习,应该掌握:

    • yield from 的基本作用是简化 for 循环,将多个可迭代对象依次产出元素。
    • yield from <generator> 的本质作用是建立委派生成器与子生成器之间的双向通道。
    • 通过这个双向通道,调用方的 .send() 和 .throw() 可以直接作用到子生成器上。
    • 子生成器通过 return 语句的返回值被封装在 StopIteration 异常中,由委派生成器中的 yield from 表达式捕获。
    • yield from 是 asyncio 模块最初采用的异步协程等待手段,后来被 async/await 语法所取代,但理解它对于理解 Python 异步编程的演进仍然非常有价值。

    完成了 yield from 这一章的学习,整个基于生成器的协程体系的关键拼图就已经完整了。下一章将进入 asyncio,实现 native 协程并揭开现代 Python 异步编程的新篇章。

    九、思考题

  • yield from <iterable> 与 for item in <iterable>: yield item 在什么条件下不是等价的?为什么?

  • 在“grouper”委派生成器示例中,为什么 while True 是必要的?如果将 while True 删除,程序的行为会发生什么变化?

  • 子生成器通过 return 语句返回值,而这个值在委托生成器中被捕获赋给变量。如果子生成器中没有显式的 return 语句,yield from 表达式的值是多少?

  • 为什么协程必须在首次调用 .send() 之前进行预激(priming)?除了 next(coro) 之外,还有哪些方式可以实现预激?

  • 在 async/await 普及之前,为什么 yield from 被认为是 asyncio 异步编程的关键能力?await 与 yield from 在语义上有何本质区别?

  • 十、本章代码索引

    本章示例代码可参考下方列表:

    文件名说明对应章节
    ch18-chain.py 使用 yield from 扁平化多个可迭代对象 第 2 节
    ch18-three-phase.py 演示 yield from 的三个阶段 第 3 节
    ch18-grouper.py 委派生成器 + 子生成器计算平均值 第 3 节
    ch18-exception.py 异常传播与 GeneratorExit 关闭 第 4 节

    代码可参考《流畅的 Python》官方代码仓库 fluentpython/example-code-2e,和 fluentpython-2nd/18b-coroutine-asyncio-basics 等相关章节的代码。

    十一、下一章预告

    第 19 章《期物和并发》

    从 yield from 的 await-like 特性过渡到原生协程(native coroutine)之后,下一章将进入并发领域:

    • concurrent.futures 模块:使用 ThreadPoolExecutor 和 ProcessPoolExecutor 实现线程级和进程级的并发。
    • 期物(Future):理解期物如何代表异步操作的结果。
    • asyncio.Future:与 concurrent.futures.Future 的异同。
    • 使用 asyncio.run() 管理事件循环:现代 asyncio 的入口点。
    • 实战案例:通过 asyncio 实现与 yield from 时代代码的清晰对比。

    第 19 章将让你快速构建起现代并发编程的知识体系,可直接用于高并发场景的架构设计。


    本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。 👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

    赞(0)
    未经允许不得转载:171主机测评 » 《流畅的Python》读书笔记19: 第四部分 控制流 - 使用 yield from
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址