深入理解Python之协程
概述
Python中在生成器中使用yield,其产生数据,被next()的调用者接收。其也会挂起生成器的执行以便调用者已经准备好接收下一个值,调用者从生成器拉数据
协程coroutine语法就像生成器。但是协程的yield通常会出现在表达式右侧。如果不产生值,那么yield之后跟着None。协程可能会从调用者接收数据,调用者会使用.send(datum)来传数据给协程,通常,调用者会将数据压入协程
有可能没有数据经过yield。除了控制数据流之外,yield能够控制调度器使多任务协作。
本文将讲述
- 生成器作为协程时的表现和状态
- 使用装饰器自动准备协程
- 调用者如何通过
.close和.throw来控制协程 - 协程如何根据终端返回值
yield的新语法使用- 模拟协程管理并发
协程如何从生成器进化
协程的基础框架出现在Python2.5,从那时起,yield能够在表达式中使用。调用者能够使用.send()来发数据,该数据变成yield的值.这样子生成器变成协程了。
Python3.3中,协程有两个语法改变
- 生成器能够返回值。在这之前会抛出
SyntaxError yield from
协程的基本行为
1 | def simple_coroutine(): |
协程有四个状态。可以使用inspect.getgeneratorstate()来确定当前的状态
GEN_CREATED:等待开始执行GEN_RUNNING: 当前被解释器执行GEN_SUSPENDED: 当前被yield表达式挂起GEN_CLOSED: 执行已经完成
1 | my_coro = simple_coroutine() |
如果创建一个协程并立即发送一个非None的值,那么会抛出异常
1 | def simple_coro2(a): |
这个方法调用时,分三步,最初初始化方法,创建协程,之后每执行一次next,执行到第一个yield表达式
协程示例
1 | def averager(): |
创建协程,启动next(coro_avg)协程,调用send修改yield右值,每次执行到yield时,会挂起协程,等待下次数据的到来
协程初始化装饰器
如果没有包装,那么我们每次使用协程都必须调用next(my_coro)。为了使协程用起来更方便.有时会使用装饰器
1 | def coroutine(func): |
中断协程和异常处理
协程内未处理的异常会传播给引起它的next或者send的调用者
中断协程的方法之一,发送一些标记告知协程退出。从Python2.5开始,生成器对象有两个方法允许客户端显式发送异常给协程–throw和close
throw: 使yield抛出异常,如果异常被生成器处理了,流程会进入下一个next。如果异常没被处理,传播给调用者
close: 抛出Generator Exit exception,如果生成器没有处理异常,不会报告任何错误给调用者.当收到GeneratorExit,生成器不能yield value,否则会抛出timeError异常,如果生成器抛出其他异常,会反馈给调用者
1 | def demo_exc_handling(self): |
协程中返回值
1 |
|
使用yield from
1 | def genYield(): |
上面两个方法的结果一样。yield from类似其他语言的await
1 | Result = namedtuple('Result', 'count average') |
案例:协程模拟离散事件
1 | DEFAULT_NUMBER_OF_TAXIS = 3 |