返回介绍

16.5 终止协程和异常处理

发布于 2024-02-05 21:59:47 字数 4535 浏览 0 评论 0 收藏 0

协程中未处理的异常会向上冒泡,传给 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文