流畅的Python - 第16章-协程
Contents
yield关键字
yield
关键字主要有两个使用方式:
yield item:
会产出一个值,提供给next(...)
的调用方。也可以不产生值,需要yield
后面不跟任何参数,返回值为Nonei = yield:
从调用方接收数据。调用方通过.send(...)
的方式将数据提供给协程。yield:
既不接收也不发送数据,纯粹作为流程控制使用。
将yield
作为协程使用时,通常也是将其作为流程控制
的调度器使用。
协程
简单的例子开始说起。
|
|
产生的结果如下:
|
|
yield
执行顺序是:先执行yield item
,再执行i = yield
。
所以,要想i = yield
生效,需要先调用next(...)
,让生成器启动起来。这个过程称为预激
。
预激
过程也可以使用装饰器来完成,可以自己定义,也可以使用系统自带的from coroutil import coroutine
。
另外,再最后一个i = yield
接收完数据后会抛出StopIteration
的异常,异常捕获后的值是原始函数的返回值,这个是正常情况。
协程有四种状态:
GEN_CREATED:
等待开始执行状态。GEN_RUNNING:
解释器正在执行状态。GEN_SUSPENDED:
在yield表达式处挂起。GEN_CLOSE:
执行结束。
使用inspect.getgeneratorstate(...)
函数能够捕获对应的状态。
协程的终止和异常处理
停止协程的方式还是让它产生异常。
- 可以隐式的
.send(...)
一个不符合要求的数据,使其产生异常退出,如果有对应的捕获异常的函数,则能够正常结束。 - 显式的调用
throw
和close
方法也能结束协程。throw
方法是致使生成器在暂停的yield
表达式处抛出指定的异常。close
方法是致使生成器在暂停的yield
表达式处抛出GeneratorExit
异常。
在
for
循环中,如果for
的对象是一个生成器的话,到最后也会抛出异常,只不过循环机制已经将异常做了相应的处理。
yield from
yield from
类似于其他语言的await
关键字(还没研究过这个关键字…)。
用法一:链接可迭代的对象
yield from
最简单的作用就是链接可迭代的对象。
|
|
打印出的结果如下:
|
|
用法二:委派生成器
yield from
还有一个更重要的用法!打开双向通道,把最外层的调用方与最内层的自生成器连接起来,两者可以直接发送和产生值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。
|
|
在上述例子中,调用方main函数
内部通过参数group
定义为生成器,在委派生成器grouper()
中使用yield from Averager(...)
时,子生成器Averager()
会获得控制权,把生成的值传给委派生成器grouper()
的调用方main函数
,即main()
直接和Averager()
接触。与此同时委派生成器grouper()
将会阻塞,直到子生成器Averager()
终止。
整个过程中,委派生成器
只做中间管道的部分。