《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 协议 ,转载请注明出处!