yield item这行代码会产出一个值,提供给next()的调用方;此外还会做出让步,暂停执行生成器,让调用方继续工作,知道需要使用另一个值再调用next()。调用方会从生成器中拉取值。
从语法上来看,协程与生成器类似,都是从定义体中包含yield关键字的函数。可是,在协程中,yield通常出现在表达式的右边(如data = yield),可以产出值,也可以不产出:如果yield关键字后面没有表达式,那么生成器产出None。协程可能会从调用方接收数据,不过调用方把提供数据给协程使用的方式是.send(data)方法。调用方会把值推送给协程。
yield关键字甚至可以不接受或传出数据。不管数据如何流动,yield都是一种流程控制工具,使用它可以实现协作式多任务;协程可以把控制器让步给中心调度程序,从而激活其他的协程。
综上,如果从根本上把yield视为控制流程的方式,这样就好理解协程了
协程可以认为是可以明确标记有某种语法元素(yield from)的阶段“暂停”函数
生成器如何进化为协程
协程的框架是在Python2.5(2006年)实现的。自此之后,yield关键字可以在表达式中使用,并且生成器增加了.send(value)方法。生成器的调用方法可以使用.send()发送数据,发送的数据会成为生成器中yield表达式的值。因此生成器可以作为协程使用。协程是指一个过程,这个过程与调用方协作,产出调用方法提供的值。
除了.send()方法,后续还增加了.throw()和.close()方法:前者作用是让调用方抛出异常,在生成器中处理;后者作用是终止生成器。
用作协程的生成器的基本行为
示例,一个简单的协程
def simple_coroutine():
print('-> coroutine started')
x = yield
print('-> coroutine received:', x)
my_coro = simple_coroutine()
print(my_coro) # 得到的是生成器对象
print(next(my_coro)) # None
print(my_coro.send(42))
打印
<generator object simple_coroutine at 0x00D957B0>
-> coroutine started
None
-> coroutine received: 42
Traceback (most recent call last):
File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 13, in <module>
print(my_coro.send(42))
StopIteration
知识点:
定义体中x = yield 这种形式,如果协程只需要从客户那接收数据,那么产出值是None,所以next(my_coro)的值为None,这个值是隐式指定的,因为yield关键字右边没有表达式。
首先要调用next(my_coro),因为生成器还没有启动,没在yield语句处暂停,所以一开始是无法发送数据的,首先要调用next(),到yield关键字处暂停。
当执行到my_coro.send(42),协程定义体中的yield表达式会得到42,然后赋值给x;协程继续运行到下一个yield表达式,或者终止。
最后,控制权流动到协程定义体的末尾,导致生成器像往常一样抛出StopIteration异常
协程的四个状态
协程可以身处四个状态中的一个。获取当前状态可以使用inspect.getgeneratorstate()函数确定,该函数会返回下面四种状态的一个:
'GEN_CREATED' :等待开始执行
'GEN_RUNNING' :解释器正在执行 (只有在多线程程序才能看到这个状态)
'GEN_SUSPENDED' :在yield表达式处暂停
'GEN_CLOSED' : 执行结束
因为send方法的参数会成为暂停的yield表达式的值,所以仅当协程处于暂停状态时才能调用send方法,例如my_coro.send(42).。
不过,如果协程还没激活(即状态为GEN_CREATED),始终要调用next(my_coro)激活协程----也可以调用my_coro.send(None)激活。
ps:如果创建协程对象后立即把None之外的值发给它,会出现以下错误,清晰明了:TypeError: can't send non-None value to a just-started generator
最先调用next(my_coro)函数这一步通常称为“预激”(prime)协程,即让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用。
from inspect import getgeneratorstate
def simple_coro2(a):
print('-> started: a=', a)
b = yield a
print('-> received: b=', b)
c = yield a + b
print('-> received: c=', c)
my_coro2 = simple_coro2(14)
print(getgeneratorstate(my_coro2))
print(next(my_coro2))
print(getgeneratorstate(my_coro2))
print(my_coro2.send(28))
print(my_coro2.send(99))
打印
GEN_CREATED
-> started: a= 14
14
GEN_SUSPENDED
-> received: b= 28
42
-> received: c= 99
Traceback (most recent call last):
File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 19, in <module>
print(my_coro2.send(99))
StopIteration
知识点:
协程在yield关键字所在的位置暂停执行。
在赋值语句中,=右边的代码在赋值前执行。因此对于b = yield a这行代码,等到客户端代码再激活协程时才会设定b的值。