深入理解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 |