Python学习之路(26)——生成器之next和send运行流程
首先还是看一下斐波那契数列:
def fib(num): count = 0 a,b = 0,1 while count < num: tmp = a a = b b = a + tmp count +=1 # print(a) #函数 yield a #生成器 print('done.....') f = fib(100) #生成了一个生成器对象,就是代表推导公式准备好了。有了yield,此时就不是函数了,就成了生成器
函数和生成器仅一步之遥。要把fib函数变成生成器,只需要把print(a)改成yield a就可以了。
生成器和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而生成器,函数在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
对于普通的生成器,第一个next调用,相当于启动生成器,会从生成器函数的第一行代码开始执行,直到第一次执行完yield语句(下例第4行)后,跳出生成器函数。
第二次next调用,进入生成器函数后,从yiedl语句的下一行(第5行)开始执行,然后重新运行到yield语句,执行后跳出生成器函数。
后面第三次调用next,依次类推。
第四次调用next,因为循环只有三次,所有没有yield语句,抛出异常。
def consumer(): r = 'here' for i in range(3): yield r r = '200 OK'+ str(i) c = consumer() print(next(c)) print(next(c)) print(next(c)) print(next(c))
执行结果如下:
here 200 OK0 200 OK1 Traceback (most recent call last): File "D:/Project/Python/Pro_py3/test.py", line 11, in <module> print(next(c)) StopIteration
了解了next()如何让包含yield的函数执行后,我们再来看另外一个非常重要的函数send(msg)。
其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,可以认为next(c)和c.send(None)作用是一样的。需要注意的是,第一次调用时,使用next()语句或send(None),不能使用send发送一个非None的值,否则会出错,因为没有Python yield语句来接收这个值。
def consumer(): r = 'here' for i in range(3): yield r r = '200 OK'+ str(i) c = consumer() print(c.send('hello')) Traceback (most recent call last): File "D:/Project/Python/Pro_py3/test.py", line 8, in <module> print(c.send('hello')) TypeError: can't send non-None value to a just-started generator
而对于有yield xxx语句的函数,send()传递的值没有用。
def consumer(): r = 'here' for i in range(3): yield r r = '200 OK '+ str(i) c = consumer() print(c.send(None)) print(c.send('hello')) print(c.send(None)) # 执行结果 here 200 OK 0 200 OK 1
那么send()应该如何使用呢?
我们首先看一下下面这个例子:
def consumer(): r = 'here' while True: n1 = yield r if not n1: return print('[CONSUMER] Consuming %s...' % n1) r = '200 OK ' + str(n1) def produce(c): c.send(None) n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r1 = c.send(n) print('[PRODUCER] Consumer return: %s' % r1) c.close() c = consumer() produce(c) # 执行结果为: [PRODUCER] Producing 1... [CONSUMER] Consuming 1... [PRODUCER] Consumer return: 200 OK 1 [PRODUCER] Producing 2... [CONSUMER] Consuming 2... [PRODUCER] Consumer return: 200 OK 2 [PRODUCER] Producing 3... [CONSUMER] Consuming 3... [PRODUCER] Consumer return: 200 OK 3 [PRODUCER] Producing 4... [CONSUMER] Consuming 4... [PRODUCER] Consumer return: 200 OK 4 [PRODUCER] Producing 5... [CONSUMER] Consuming 5... [PRODUCER] Consumer return: 200 OK 5
我们看看send的执行顺序。
当第一次send(None)(对应第11行,相当于next())时,启动生成器,从生成器函数consumer()的第一行代码开始执行,直到第一次执行完yield(对应第4行)后,跳出生成器函数。这个过程中,n1一直没有定义。
下面进行到send(1)时,进入生成器函数,注意这里与调用next不同。从第4行开始,把传递进去的值1赋值给n1,但是并不执行yield部分,然后继续从yield的下一行语句继续执行,然后再次重新运行到yield语句,执行后,跳出生成器函数。
后面以此类推。
send和next相比,只是开始多了一次赋值的动作,其他运行流程是相同的。
下面的例子:
import time def consumer(name): print('%s准备吃包子啦!' % name) while True: baozi = yield print('包子[%s]来了,被[%s]吃了'% (baozi, name)) def producer(name): c1 = consumer("A") c2 = consumer("B") next(c1) next(c2) print('%s开始准备做包子啦!' % name) for i in range(10): time.sleep(1) print('做了2个包子!') c1.send(i) c2.send(i) producer('nicolas')