协程定义
协程的底层架构是在pep342 中定义,并在python2.5 实现的。
python2.5 中,yield关键字可以在表达式中使用,而且生成器API中增加了 .send(value)方法。生成器可以使用.send(...)方法发送数据,发送的数据会成为生成器函数中yield表达式的值。
协程是指一个过程,这个过程与调用方协作,产出有调用方提供的值。因此,生成器可以作为协程使用。
除了 .send(...)方法,pep342 和添加了 .throw(...)(让调用方抛出异常,在生成器中处理)和.close()(终止生成器)方法。
python3.3后,pep380对生成器函数做了两处改动:
生成器可以返回一个值;以前,如果生成器中给return语句提供值,会抛出SyntaxError异常。
引入yield from 语法,使用它可以把复杂的生成器重构成小型的嵌套生成器,省去之前把生成器的工作委托给子生成器所需的大量模板代码。
首先说明一下,协程有四个状态,可以使用inspect.getgeneratorstate(...)函数确定:
GEN_CREATED # 等待开始执行
GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)
GEN_SUSPENDED # 在yield表达式处暂停
GEN_CLOSED # 执行结束
#! -*- coding: utf-8 -*-
import inspect
# 协程使用生成器函数定义:定义体中有yield关键字。
defsimple_coroutine():
print('-> coroutine started') # yield 在表达式中使用;如果协程只需要从客户那里接收数据,yield关键字右边不需要加表达式(yield默认返回None)
x = yield
print('-> coroutine received:', x)
my_coro = simple_coroutine()# 和创建生成器的方式一样,调用函数得到生成器对象。
print(inspect.getgeneratorstate(my_coro))
# 协程处于 GEN_CREATED (等待开始状态)my_coro.send(None)
# 首先要调用next()函数,因为生成器还没有启动,没有在yield语句处暂停,所以开始无法发送数据
# 发送 None 可以达到相同的效果 my_coro.send(None)
next(my_coro)# 此时协程处于 GEN_SUSPENDED (在yield表达式处暂停)
print(inspect.getgeneratorstate(my_coro))
# 调用这个方法后,协程定义体中的yield表达式会计算出42;现在协程会恢复,一直运行到下一个yield表达式,或者终止。
my_coro.send(42)
print(inspect.getgeneratorstate(my_coro))
运行上述代码,输出结果如下
GEN_CREATED
-> coroutine started
GEN_SUSPENDED
-> coroutine received: 42# 这里,控制权流动到协程定义体的尾部,导致生成器像往常一样抛出StopIteration异常
Traceback (most recent call last):
File "/Users/gs/coroutine.py", line 18, in <module>
my_coro.send(42)
StopIteration
send方法的参数会成为暂停yield表达式的值,所以,仅当协程处于暂停状态是才能调用send方法。
如果协程还未激活(GEN_CREATED 状态)要调用next(my_coro) 激活协程,也可以调用my_coro.send(None)
如果创建协程对象后立即把None之外的值发给它,会出现下述错误:
>>> my_coro = simple_coroutine()
>>> my_coro.send(123)
Traceback (most recent call last):
File "/Users/gs/coroutine.py", line 14, in <module>
my_coro.send(123)
TypeError: can't send non-None value to a just-started generator
仔细看错误消息
can't send non-None value to a just-started generator
最先调用next(my_coro) 这一步通常称为”预激“(prime)协程---即,让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用。
再看一个两个值得协程
defsimple_coro2(a):
print('-> coroutine started: a =', a)
b = yield a
print('-> Received: b =', b)
c = yield a + b
print('-> Received: c =', c)
my_coro2 = simple_coro2(14)
print(inspect.getgeneratorstate(my_coro2))# 这里inspect.getgeneratorstate(my_coro2) 得到结果为 GEN_CREATED (协程未启动)
next(my_coro2)
# 向前执行到第一个yield 处 打印 “-> coroutine started: a = 14”
# 并且产生值 14 (yield a 执行 等待为b赋值)
print(inspect.getgeneratorstate(my_coro2))
# 这里inspect.getgeneratorstate(my_coro2) 得到结果为 GEN_SUSPENDED (协程处于暂停状态)
my_coro2.send(28)
# 向前执行到第二个yield 处 打印 “-> Received: b = 28”
# 并且产生值 a + b = 42(yield a + b 执行 得到结果42 等待为c赋值)
print(inspect.getgeneratorstate(my_coro2))
# 这里inspect.getgeneratorstate(my_coro2) 得到结果为 GEN_SUSPENDED (协程处于暂停状态)
my_coro2.send(99)
# 把数字99发送给暂停协程,计算yield 表达式,得到99,然后把那个数赋值给c 打印 “-> Received: c = 99”
# 协程终止,抛出StopIteration
运行上述代码,输出结果如下
GEN_CREATED
-> coroutine started: a = 14
GEN_SUSPENDED
-> Received: b = 28
-> Received: c = 99
Traceback (most recent call last):
File "/Users/gs/coroutine.py", line 37, in <module>
my_coro2.send(99)
StopIteration