- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.5 终止协程和异常处理
协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)。示例 16-7 举例说明如何使用示例 16-6 中由装饰器定义的 averager 协程。
示例 16-7 未处理的异常会导致协程终止
>>> from coroaverager1 import averager >>> coro_avg = averager() >>> coro_avg.send(40) # ➊ 40.0 >>> coro_avg.send(50) 45.0 >>> coro_avg.send('spam') # ➋ Traceback (most recent call last): ... TypeError: unsupported operand type(s) for +=: 'float' and 'str' >>> coro_avg.send(60) # ➌ Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
❶ 使用 @coroutine 装饰器装饰的 averager 协程,可以立即开始发送值。
❷ 发送的值不是数字,导致协程内部有异常抛出。
❸ 由于在协程内没有处理异常,协程会终止。如果试图重新激活协程,会抛出 StopIteration 异常。
出错的原因是,发送给协程的 'spam' 值不能加到 total 变量上。
示例 16-7 暗示了终止协程的一种方式:发送某个哨符值,让协程退出。内置的 None 和 Ellipsis 等常量经常用作哨符值。Ellipsis 的优点是,数据流中不太常有这个值。我还见过有人把 StopIteration 类(类本身,而不是实例,也不抛出)作为哨符值;也就是说,是像这样使用的:my_coro.send(StopIteration)。
从 Python 2.5 开始,客户代码可以在生成器对象上调用两个方法,显式地把异常发给协程。
这两个方法是 throw 和 close。
generator.throw(exc_type[, exc_value[, traceback]])
致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw 方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。
generator.close()
致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出 RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。
生成器对象方法的官方文档深藏在 Python 语言参考手册中,参见“6.2.9.1.Generator-iterator methods”。
下面举例说明如何使用 close 和 throw 方法控制协程。示例 16-8 列出的是接下来的例子使用的 demo_exc_handling 函数。
示例 16-8 coro_exc_demo.py:学习在协程中处理异常的测试代码
class DemoException(Exception): """为这次演示定义的异常类型。""" def demo_exc_handling(): print('-> coroutine started') while True: try: x = yield except DemoException: ➊ print('*** DemoException handled. Continuing...') else: ➋ print('-> coroutine received: {!r}'.format(x)) rai se RuntimeError('This line should never run.') ➌
❶ 特别处理 DemoException 异常。
❷ 如果没有异常,那么显示接收到的值。
❸ 这一行永远不会执行。
示例 16-8 中的最后一行代码不会执行,因为只有未处理的异常才会中止那个无限循环,而一旦出现未处理的异常,协程会立即终止。
demo_exc_handling 函数的常规用法如示例 16-9 所示。
示例 16-9 激活和关闭 demo_exc_handling,没有异常
>>> exc_coro = demo_exc_handling() >>> next(exc_coro) -> coroutine started >>> exc_coro.send(11) -> coroutine received: 11 >>> exc_coro.send(22) -> coroutine received: 22 >>> exc_coro.close() >>> from inspect import getgeneratorstate >>> getgeneratorstate(exc_coro) 'GEN_CLOSED'
如果把 DemoException 异常传入 demo_exc_handling 协程,它会处理,然后继续运行,如示例 16-10 所示。
示例 16-10 把 DemoException 异常传入 demo_exc_handling 不会导致协程中止
>>> exc_coro = demo_exc_handling() >>> next(exc_coro) -> coroutine started >>> exc_coro.send(11) -> coroutine received: 11 >>> exc_coro.throw(DemoException) *** DemoException handled. Continuing... >>> getgeneratorstate(exc_coro) 'GEN_SUSPENDED'
但是,如果传入协程的异常没有处理,协程会停止,即状态变成 'GEN_CLOSED'。示例 16-11 演示了这种情况。
示例 16-11 如果无法处理传入的异常,协程会终止
>>> exc_coro = demo_exc_handling() >>> next(exc_coro) -> coroutine started >>> exc_coro.send(11) -> coroutine received: 11 >>> exc_coro.throw(ZeroDivisionError) Traceback (most recent call last): ... ZeroDivisionError >>> getgeneratorstate(exc_coro) 'GEN_CLOSED'
如果不管协程如何结束都想做些清理工作,要把协程定义体中相关的代码放入 try/finally 块中,如示例 16-12。
示例 16-12 coro_finally_demo.py:使用 try/finally 块在协程终止时执行操作
class DemoException(Exception): """为这次演示定义的异常类型。""" def demo_finally(): print('-> coroutine started') try: while True: try: x = yield except DemoException: print('*** DemoException handled. Continuing...') else: print('-> coroutine received: {!r}'.format(x)) finally: print('-> coroutine ending')
Python 3.3 引入 yield from 结构的主要原因之一与把异常传入嵌套的协程有关。另一个原因是让协程更方便地返回值。请继续往下读,了解详情。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论