- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
16.2 用作协程的生成器的基本行为
示例 16-1 展示了协程的行为。
示例 16-1 可能是协程最简单的使用演示
>>> def simple_coroutine(): # ➊ ... print('-> coroutine started') ... x = yield # ➋ ... print('-> coroutine received:', x) ... >>> my_coro = simple_coroutine() >>> my_coro # ➌ <generator object simple_coroutine at 0x100c2be10> >>> next(my_coro) # ➍ -> coroutine started >>> my_coro.send(42) # ➎ -> coroutine received: 42 Traceback (most recent call last): # ➏ ... StopIteration
❶ 协程使用生成器函数定义:定义体中有 yield 关键字。
❷ yield 在表达式中使用;如果协程只需从客户那里接收数据,那么产出的值是 None——这个值是隐式指定的,因为 yield 关键字右边没有表达式。
❸ 与创建生成器的方式一样,调用函数得到生成器对象。
❹ 首先要调用 next(...) 函数,因为生成器还没启动,没在 yield 语句处暂停,所以一开始无法发送数据。
❺ 调用这个方法后,协程定义体中的 yield 表达式会计算出 42;现在,协程会恢复,一直运行到下一个 yield 表达式,或者终止。
❻ 这里,控制权流动到协程定义体的末尾,导致生成器像往常一样抛出 StopIteration 异常。
协程可以身处四个状态中的一个。当前状态可以使用 inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。
'GEN_CREATED'
等待开始执行。
'GEN_RUNNING'
解释器正在执行。1
1只有在多线程应用中才能看到这个状态。此外,生成器对象在自己身上调用 getgeneratorstate 函数也行,不过这样做没什么用。
'GEN_SUSPENDED'
在 yield 表达式处暂停。
'GEN_CLOSED'
执行结束。
因为 send 方法的参数会成为暂停的 yield 表达式的值,所以,仅当协程处于暂停状态时才能调用 send 方法,例如 my_coro.send(42)。不过,如果协程还没激活(即,状态是 'GEN_CREATED'),情况就不同了。因此,始终要调用 next(my_coro) 激活协程——也可以调用 my_coro.send(None),效果一样。
如果创建协程对象后立即把 None 之外的值发给它,会出现下述错误:
>>> my_coro = simple_coroutine() >>> my_coro.send(1729) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't send non-None value to a just-started generator
注意错误消息,它表述得相当清楚。
最先调用 next(my_coro) 函数这一步通常称为“预激”(prime)协程(即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)。
下面举个产出多个值的例子,以便更好地理解协程的行为,如示例 16-2 所示。
示例 16-2 产出两个值的协程
>>> def simple_coro2(a): ... print('-> Started: a =', a) ... b = yield a ... print('-> Received: b =', b) ... c = yield a + b ... print('-> Received: c =', c) ... >>> my_coro2 = simple_coro2(14) >>> from inspect import getgeneratorstate >>> getgeneratorstate(my_coro2) ➊ 'GEN_CREATED' >>> next(my_coro2) ➋ -> Started: a = 14 14 >>> getgeneratorstate(my_coro2) ➌ 'GEN_SUSPENDED' >>> my_coro2.send(28) ➍ -> Received: b = 28 42 >>> my_coro2.send(99) ➎ -> Received: c = 99 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> getgeneratorstate(my_coro2) ➏ 'GEN_CLOSED'
❶ inspect.getgeneratorstate 函数指明,处于 GEN_CREATED 状态(即协程未启动)。
❷ 向前执行协程到第一个 yield 表达式,打印 -> Started: a = 14 消息,然后产出 a 的值,并且暂停,等待为 b 赋值。
❸ getgeneratorstate 函数指明,处于 GEN_SUSPENDED 状态(即协程在 yield 表达式处暂停)。
❹ 把数字 28 发给暂停的协程;计算 yield 表达式,得到 28,然后把那个数绑定给 b。打印 -> Received: b = 28 消息,产出 a + b 的值(42),然后协程暂停,等待为 c 赋值。
❺ 把数字 99 发给暂停的协程;计算 yield 表达式,得到 99,然后把那个数绑定给 c。打印 -> Received: c = 99 消息,然后协程终止,导致生成器对象抛出 StopIteration 异常。
❻ getgeneratorstate 函数指明,处于 GEN_CLOSED 状态(即协程执行结束)。
关键的一点是,协程在 yield 关键字所在的位置暂停执行。前面说过,在赋值语句中,= 右边的代码在赋值之前执行。因此,对于 b = yield a 这行代码来说,等到客户端代码再激活协程时才会设定 b 的值。这种行为要花点时间才能习惯,不过一定要理解,这样才能弄懂异步编程中 yield 的作用(后文探讨)。
simple_coro2 协程的执行过程分为 3 个阶段,如图 16-1 所示。
(1) 调用 next(my_coro2),打印第一个消息,然后执行 yield a,产出数字 14。
(2) 调用 my_coro2.send(28),把 28 赋值给 b,打印第二个消息,然后执行 yield a + b,产出数字 42。
(3) 调用 my_coro2.send(99),把 99 赋值给 c,打印第三个消息,协程终止。
图 16-1:执行 simple_coro2 协程的 3 个阶段(注意,各个阶段都在 yield 表达式中结束,而且下一个阶段都从那一行代码开始,然后再把 yield 表达式的值赋给变量)
下面来看一个稍微复杂的协程示例。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论