yield与send实现协程操作
之前我们说过,在函数内部含有yield语句即称为生成器。
下面,我们来看看在函数内部含有yield语句达到的效果。首先,我们来看看以下代码:
def foo(): while True: x = yield print("value:",x) g = foo() # g是一个生成器 next(g) # 程序运行到yield就停住了,等待下一个next g.send(1) # 我们给yield发送值1,然后这个值被赋值给了x,并且打印出来,然后继续下一次循环停在yield处 g.send(2) # 同上 next(g) # 没有给x赋值,执行print语句,打印出None,继续循环停在yield处
我们都知道,程序一旦执行到yield就会停在该处,并且将其返回值进行返回。上面的例子中,我们并没有设置返回值,所有默认程序返回的是None。我们通过打印语句来查看一下第一次next的返回值:
print(next(g)) ####输出结果##### None
正如我们所说的,程序返回None。接着程序往下执行,但是并没有看到next()方法。为什么还会继续执行yield语句后面的代码呢?这是因为,send()方法具有两种功能:第一,传值,send()方法,将其携带的值传递给yield,注意,是传递给yield,而不是x,然后再将其赋值给x;第二,send()方法具有和next()方法一样的功能,也就是说,传值完毕后,会接着上次执行的结果继续执行,知道遇到yield停止。这也就为什么在调用g.send()方法后,还会打印出x的数值。有了上面的分析,我们可以很快知道,执行了send(1)后,函数被停止在了yield处,等待下一个next()的到来。程序往下执行,有遇到了send(2),其执行流程与send(1)完全一样。
有了上述的分析,我们可以总结出send()的两个功能:1.传值;2.next()。
既然send()方法有和next一样的作用,那么我们可不可以这样做:
def foo(): while True: x = yield print("value:",x) g = foo() g.send(1) #执行给yield传值,这样行不行呢?
很明显,是不行的。
TypeError: can't send non-None value to a just-started generator
错误提示:不能传递一个非空值给一个未启动的生成器。
也就是说,在一个生成器函数未启动之前,是不能传递数值进去。必须先传递一个None进去或者调用一次next(g)方法,才能进行传值操作。至于为什么要先传递一个None进去,可以看一下官方说法。
Because generator-iterators begin execution at the top of the generator's function body, there is no yield expression to receive a value when the generator has just been created. Therefore, calling send() with a non-None argument is prohibited when the generator iterator has just started, and a TypeError is raised if this occurs (presumably due to a logic error of some kind). Thus, before you can communicate with a coroutine you must first call next() or send(None) to advance its execution to the first yield expression.