🇨🇳
99 的博客
  • 久韭
  • Python
    • 你真的“创建”了类吗?
    • MRO 三定律
    • 描述器学习指南
    • 描述器实现 property
    • 如何做一个好中介
    • PEP 阅读清单
    • Python 双刃剑
    • 类型标注
    • “类”的渐进式剖析
  • Network
    • IPv6
    • 大道至简
    • DNS 根域
    • RFC 阅读清单
    • 基础公共服务
      • DNS
      • NTP
    • MAC 地址厂商信息
    • 点对点的中介
  • Windows
    • Windows 使用小技巧
  • Linux
    • SSH 技高一筹
    • nftables 速成
    • Linux 30 周年
    • 新时代 Linux 命令
    • 树莓派实时性优化
    • Bash DEBUG 一行搞定
  • 佳作收藏
    • 美文
      • 短篇
        • 爱
        • 弟弟
        • 暗途
        • 噩梦
        • 想象
        • 底线
        • 疯人
        • 雪兔
        • 夺妻
        • 闹钟
        • 虐猫
        • 做起来
        • 陈小手
        • 道德极限
        • 符号控制
        • 与人为友
        • 消失的人
        • 时间旅行
        • 情债肉偿
        • 铁血恋爱
        • 太阳黑子
        • 蚂蚁人生
        • 风骚和魅力
        • 我只要一种
        • 不朽的失眠
        • 秋天的怀念
        • 午夜的汽笛
        • 多美的故事
        • 相信不相信
        • 清晨的变故
        • 真实的高贵
        • 神秘领奖人
        • 幸福的夜晚
        • 幸福的生日
        • 我有事找你
        • 我是个窃贼
        • 最担心的是你
        • 客厅里的爆炸
        • 假如是你的话
        • 愿你慢慢长大
        • 民意与伪民意
        • 婚姻中没有天堂
        • 一种深久的不安
        • 非走不可的弯路
        • 单调产生的快乐
        • 我只是讨厌屈服
        • 跳槽只为“软福利”
        • 你的阅读造就了你
        • 聪明人和傻子和奴才
        • 你永远有做不完的事
        • 精神病院里的年轻人
        • 一位短跑运动员的孤独
        • 那些你所不知道的大事
        • 穿过大半个中国去睡你
        • 我觉得自己会永远生猛下去
      • 长篇
        • 早餐
        • 波心
        • 老王
        • 翻浆
        • 小猫
        • 良娼
        • 自信
        • 黑羊
        • 猫人
        • 斜眼
        • 识人
        • 身价
        • 肯肯舞
        • 狗和猫
        • 热包子
        • 黄裙子
        • 老江湖
        • 逃脱术
        • 侯银匠
        • 太原诅咒
        • 死亡花朵
        • 罗马惊艳
        • 刺青时代
        • 好嘴杨巴
        • 扼杀胎儿
        • 纪念照片
        • 紫色人形
        • 天外飞石
        • 戒烟公司
        • 蔡二少爷
        • 女人的星球
        • 一千张糖纸
        • 爱情与投资
        • 赌徒的遗书
        • 飞越流水线
        • 跟踪狂入门
        • 一桩自杀案
        • 机舱里的钟声
        • 桌子还是桌子
        • 一小时的故事
        • 不存在的女友
        • 谁在编造历史
        • 父亲坐在黑暗中
        • 一个小小的建议
        • 彬彬有礼的强盗
        • 18 本画册的爱恋
        • 河流最蓝的地方
        • 好人总会有人疼
        • 偷听谈话的妙趣
        • 公主整夜不能睡
        • 他们那时多有趣
        • 布莱克·沃兹沃斯
        • 坐在路边鼓掌的人
        • 最伟大的科幻小说
        • 你丈夫是干什么的
        • 谁也看不见的阳台
        • 上午打瞌睡的女孩
    • 段子
      • 程序员段子
      • 二次元段子
      • 大老师片段
      • 金句
      • 喝痰
      • 数学家
      • 牛子
      • 男 ♂ 语
      • 甲方乙方
      • 新编故事四则
      • 没人知道大东为何在此舞蹈
  • 日常随记
    • 坑
    • 豆知识
    • 逻辑推断
    • 观影后记
    • LaMDA 有意识吗?
    • VS Code Vim 速记
    • QQ 空间表情符号
    • ZEN of DEBUG
    • 数据收集的途径
    • 呼叫转移设置方式
    • 空中浩劫 ACI 经典
    • 联机海难特色简述
    • 智能家居入门指南
    • 财政学笔记
    • 行为与实验经济学笔记
    • 电商管理学笔记
由 GitBook 提供支持
在本页
  • 0. 声明
  • 1. 基础回顾
  • 1.0 生成器(Generators) yield
  • 1.1 委派生成器 yield from
  • 2. 原理解析
  • 2.0 完整语义
  • 2.1 代码示范
  • 3. 参考源
在GitHub上编辑
  1. Python

如何做一个好中介

yield from 委派生成器语句解析

上一页描述器实现 property下一页PEP 阅读清单

最后更新于1年前

0. 声明

不从 0 开始难不成从 1 开始?你知道吗,世上只有 10 种人,程序员和非程序员;

本文假设您已经掌握 生成器(Generators) 相关的知识

1. 基础回顾

1.0 生成器(Generators) yield

Python 生成器本质是一种 迭代器 ,但功能更强大。

在 Python 2.2 版本(2001 年)中,经由 引入了 生成器 的概念。其最初是为了解决开发者经常遇到的一个小问题:

当生产者函数完成一项艰苦的工作以至于 需要维持所生成的值之间的状态 时,大多数编程语言都无法提供一种愉悦而有效的解决方案,只能将回调函数添加到生产者的参数列表中,以对每个生成的值进行调用。

自此以后,Python 的关键字大家庭里又多了一员 yield 。而生成器这个概念,也伴随着 Python 走过了 20 年 的风风雨雨,至今,它已经是 Python 中不可分离的重要特性。

探讨生成器的重要性及其延伸开来的广阔特性(如基于生成器的协程)足以另起一篇万字长文,故在此不多做涉猎,但可以肯定的是:

不用生成器,何谈 Pythonic?

1.1 委派生成器 yield from

在 Python 3.3 版本(2011 年)中,经由 PEP 380 引入了这个新的语法,其目的主要是为了解决伴随着生成器在 Python 中的大量使用而出现的生成器间组合的需求。

首先我们来看一看各种官方资料中有关这一委派的说明好了:

  • 供生成器将其部分操作委托给另一生成器。这允许包含 yield 的一段代码被分解并放置在另一个生成器中。此外,允许子生成器返回一个值,并且该值可用于委派生成器。

感觉不说人话啊有莫有。。。。。。没事不要急,咱们先把这死板的概念放一边,来看看这玩意都有什么用:

  • 这个语法的提出促进了 基于生成器的协程 的发展,如今所使用的协程关键字 async await 以及标准库中的 asyncio 就是最好的例子。如果你也曾为这些复杂的概念头疼过 XD,那你必然在回溯报错的过程中多多少少见到了底层一个又一个的 yield from 。

协程相关内容超出本文讨论范围,在这里不作展开。

还是不懂?那我们用代码来说话:

def no_yield_from_generator():
    iterable = (0, 1, 2, 3)
    for i in iteralbe:
        yield i

def yield_from_generator():
    iterable = (0, 1, 2, 3)
    yield from iterable

简单直白,一眼就能懂意思是吧?yield from 就是可以让我们少写一层 for ?

不要急,这只是它最简单的作用罢了。你有没有想过,如果这个 iterable 本身也是个生成器,那么外面一层生成器的 send() throw() close() 被调用后要怎么办?

委派生成器 yield from 最重要的作用是当一个 中介,协调两个嵌套的生成器的工作 。

咱当时理解到这里的时候,心生疑惑了:不就是个委托调用吗?能有多难,咱自己做不行吗?

咱现在可以明确的说,还真的挺难的,原因且听咱细细道来。

生成器用着方便,但其底层的实现还是相对复杂的。更不幸的是,在基于生成器的协程发展的过程中,为生成器引入了 send() throw() close() 等新方法,这让委托调用变得复杂化_(中介哪有那么好当 ╮(╯▽╰)╭)_。 尽管可以自己来手工实现一些简单的,但随着条件的复杂化和调用的多样化,这些仅关注于部分内容的处理都会变成埋下的 雷 。 相信我,你不会想亲自体验一次的 ╰(艹皿艹 )! 所以这种 黑魔法研究 的任务还是交给官方开发组来做吧。

yield from 中介所,多个中间商赚差价,让你的代码更好看!

2. 原理解析

2.0 完整语义

首先,既然 yield from 涉及了两个生成器,为了便于后文叙述,我们需要先区分它们。以代码为例:

def generator():
    for i in range(9):
        yield i

def delegated_generator():
    yield from generator()    
    

外层的生成器 delegated_generator() 称作 委派生成器,内层的这个 generator() 就简单称之为 迭代器 就好。

生成器本质上是一种特殊的迭代器。

yield from 的具体语义如下:

  • 作为一个中介,首先 迭代器产生的任何值都将直接传递给调用方 。

  • 使用 send() 发送给委派生成器的任何值都将直接传递给迭代器:

    • 如果发送的值为 None,则调用迭代器的 __next__() 方法。

    • 如果发送的值不为 None,则调用迭代器的 send() 方法。如果调用引发 StopIteration 则恢复委派生成器的执行。任何其他异常都会传播到委派生成器。

  • **抛出到委派生成器中的 GeneratorExit 以外的异常被传递给迭代器的 throw() 方法。**如果调用引发 StopIteration,则恢复委派生成器的执行。任何其他异常都会传播到委派生成器。

  • **如果在委派生成器中抛出 GeneratorExit 异常,或者委派生成器的 close() 方法被调用,则尝试调用(如果有则调用)迭代器的 close() 方法如果。**如果此调用导致异常,则将其传播到委派生成器。否则,在委派生成器中引发 GeneratorExit。

  • yield from 表达式的值 是迭代器终止时引发的**StopIteration 异常的第一个参数**。

  • 在生成器中,return expr 会导致在退出生成器时引发 StopIteration(expr) 。

2.1 代码示范

RESULT = yield from EXPR

↑↑↑↑↑↑ 上面这个语句,实际就相当于 ↓↓↓↓↓↓

iterator = iter(EXPR)
try:
    yield_value = next(iterator)
except StopIteration as exception:
    result_value = exception.value
else:
    while 1:
        try:
            send_value = yield yield_value
        except GeneratorExit as generator_exit:
            try:
                close_method = iterator.close
            except AttributeError:
                pass
            else:
                close_method()
            raise generator_exit
        except BaseException as base_exception:
            exception_info = sys.exc_info()
            try:
                throw_method = iterator.throw
            except AttributeError:
                raise base_exception
            else:
                try:
                    yield_value = throw_method(*exception_info)
                except StopIteration as stop_iteration:
                    result_value = stop_iteration.value
                    break
        else:
            try:
                if send_value is None:
                    yield_value = next(iterator)
                else:
                    yield_value = iterator.send(send_value)
            except StopIteration as stop_iteration:
                result_value = stop_iteration.value
                break
RESULT = result_value

3. 参考源

当使用 yield from <expr> 时,所提供的表达式必须是一个 可迭代对象 。迭代该可迭代对象所产生的值会被直接传递给当前生成器方法的调用者。任何通过 传入的值以及任何通过 传入的异常 如果有适当的方法则会被传给下层迭代器 。如果不是这种情况,那么 将引发 或 ,而 将立即引发所转入的异常。

PEP 255
send()
throw()
send()
AttributeError
TypeError
throw()
PEP 255 -- Simple Generators
PEP 342 -- Coroutines via Enhanced Generators
PEP 380 -- Syntax for Delegating to a Subgenerator