>>> from env_helper import info; info()
页面更新时间: 2024-03-29 16:20:49
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

1.16. 协程

如果 Python 书籍有一定的指导作用,那么(协程就是)文档最匮乏、最鲜为人知的 Python 特性,因此表面上看是最无用的特性。 ——David Beazley, Python 图书作者

在“生成器”章节中我们认识了 yield 语句。但 yield 的作用不只是在生成器运行过程中返回一个值,还可以从调用方拿回来一个值(.send(datum)),甚至一个异常(.throw(exc))。
由此依赖,yield 语句就成为了一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其它的协程。

从根本上把 yield 视作控制流程的方式,这样就好理解协程了。

本章涵盖以下话题: * 生成器作为协程使用时的行为和状态 * 使用装饰器自动预激协程 * 调用方如何使用生成器对象的 .close().throw(...) 方法控制协程 * 协程终止时如何返回值 * yield from 新句法的用途和语义 * 使用案例——使用协程管理仿真系统中的并发活动

协程的四种状态: * GEN_CREATED: 等待开始执行 * GEN_RUNNING: 解释器正在执行 * GEN_SUSPENDED: 在 yield 表达式处暂停 * GEN_CLOSED: 执行结束

>>> # 最简单的协程使用演示
>>> from inspect import getgeneratorstate
>>>
>>> def simple_coroutine():
>>>     # GEN_RUNNING 状态
>>>     print("Coroutine started")
>>>     x = yield
>>>     print("Couroutine received:", x)
>>>
>>> my_coro = simple_coroutine()
>>> print(getgeneratorstate(my_coro)) # GEN_CREATED
>>> next(my_coro)                     # “预激”(prime)协程,使它能够接收来自外部的值
>>> print(getgeneratorstate(my_coro)) # GEN_SUSPENDED
>>> try:
>>>     my_coro.send(42)
>>> except StopIteration as e:
>>>     print('StopIteration')
>>> print(getgeneratorstate(my_coro)) # GEN_CLOSED
>>> # 产出多个值的协程
>>> def async_sum(a=0):
>>>     s = a
>>>     while True:
>>>         n = yield s
>>>         s += n
>>>
>>> asum = async_sum()
>>> next(asum)
>>> for i in range(1, 11):
>>>     print(i, asum.send(i))
>>> asum.close()                 # 如果协程不会自己关闭,我们还可以手动终止协程
>>> asum.send(11)
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-13-0daab1220103> in <module>()
     11     print(i, asum.send(i))
     12 asum.close()                 # 如果协程不会自己关闭,我们还可以手动终止协程
---> 13 asum.send(11)


StopIteration:
协程手动终止的注意事项:
调用 gen.closed() 后,生成器内的 yield 语句会抛出 GeneratorExit 异常。如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报错。
如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出 RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。

协程内异常处理的示例见官方示例 Repo

1.16.1. yield from

在协程中,yield from 语句的主要功能是打开双向通道,把外层的调用方与内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。
这种夹在中间的生成器,我们称它为“委派生成器”。

子生成器迭代结束后返回(return)的值,会交给 yield from 函数。

注意:yield from 语句会预激生成器,所以与用来预激生成器的装饰器不能放在一起用,否则会出问题。

>>> # 委派生成器
>>> def async_sum(a=0):
>>>     s = a
>>>     while True:
>>>         try:
>>>             n = yield s
>>>         except Exception as e:
>>>             print('Caught exception', e)
>>>             return s
>>>         s += n
>>>
>>> def middleware():
>>>     x = yield from async_sum()
>>>     print('Final result:', x)
>>>
>>> asum = middleware()
>>> next(asum)
>>> for i in range(1, 11):
>>>     print(asum.send(i))
>>>
>>> _ = asum.throw(ValueError)
1
3
6
10
15
21
28
36
45
55
Caught exception
Final result: 55
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-16-27762d9b83ae> in <module>()
     19     print(asum.send(i))
     20
---> 21 _ = asum.throw(ValueError)


StopIteration:

关于“任务式”协程,书中给出了一个简单的例子,用于执行离散事件仿真,仔细研究一下可以对协程有个简单的认识。