《Fluent Python》笔记 协程
生成器作为协程
协程是指一个过程, 这个过程与调用方协作, 产出由调用方提供的值。
协程使用的简单演示(用作协程的生成器):
1 |
|
协程有四个状态,协程当前的状态可以使用inspect.getgeneratorstate()
函数确定, 该函数会返回下述字符串中的一个。
**’GEN_CREATED’**:等待开始执行。
**’GEN_RUNNING’**:解释器正在执行。
**’GEN_SUSPENDED’**:在 yield
表达式处暂停。
**’GEN_CLOSED’**:执行结束。
因为 send
方法的参数会成为暂停的 yield
表达式的值, 所以, 仅当协程处于暂停状态(’GEN_SUSPENDED’)时才能调用 send
方法,否则会报错。
生成器实例化后得到的协程my_coro
处于’GEN_CREATED’状态,通过next(my_coro)
(也可以调用my_coro.send(None)
,效果相同)激活协程变为’GEN_RUNNING’状态(第一次次激活叫做预激)。运行到yield
表达式变为’GEN_SUSPENDED’状态,协程定义体执行结束变为’GEN_CLOSED’状态。
通过装饰器预激协程
因为预激是使用协程的关键步骤,为了简化协程的用法,有时会使用一个预激装饰器,这样可以避免忘记预激操作。预激装饰器示例:
1 |
|
终止协程和异常处理
未处理的异常会导致协程终止。
示例代码如下:
1 |
|
1 |
|
重要方法:
generator.send(...)
如果发送给协程的值(通常可以使用内置的
None
和Ellipsis
)在协程定义体中参与运算抛出异常,且这个抛出的异常未处理,那么就会导致协程终止。generator.throw(exc_type[, exc_value[, traceback]])
使协程在暂停的
yield
表达式处抛出指定的异常(exc_type
)。 如果协程处理了抛出的异常, 代码会向前执行到下一个yield
表达式, 而产出的值会成为调用generator.throw
方法得到的返回值。 如果协程没有处理抛出的异常, 异常会向上冒泡, 传到调用方的上下文中。并且协程也会终止。generator.close()
致使协程在暂停的
yield
表达式处抛出GeneratorExit
异常。如果协程没有处理这个异常, 或者抛出了StopIteration
异常(通常是指运行到结尾),调用方不会报错(此时协程终止退出)。 如果收到GeneratorExit
异常, 协程一定不能产出值, 否则解释器会抛出RuntimeError
异常。协程抛出的其他异常会向上冒泡, 传给调用方。并且协程也会终止。
虽然上面所说的协程其实本质上是生成器对象,此时生成器对象的行为体现了协程。
yield from在协程中的运用
yield from
的主要功能是打开双向通道, 把最外层的调用方与最内层的子生成器连接起来, 这样二者可以直接发送和产出值, 还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。 有了这个结构, 协程可以通过以前不可能的方式委托职责。
委派生成器
包含yield from <iterable>
表达式的生成器函数。子生成器
从yield from
表达式中<iterable>
部分获取的生成器。
该结构运行流程如下:
委派生成器在 yield from
表达式处暂停时, 调用方可以直接把数据发给子生成器, 子生成器再把产出的值发给调用方。 子生成器返回之后, 解释器会抛出 StopIteration
异常, 并把返回值附加到异常对象上, 此时委派生成器会恢复。
1 |
|
1 |
|
grouper
发送的每个值都会经由 yield from
处理, 通过管道传给averager
实例。 grouper
会在 yield from
表达式处暂停, 等待averager
实例处理客户端发来的值。 averager
实例运行完毕后, 返回的值绑定到 results[key]
上。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!