关于协程的理解
参考文档:http://www.cnblogs.com/coderzh/articles/1202040.html(我觉得这个作者讲的很好,让我茅塞顿开,所以借鉴了不少,如有侵权嫌疑请私戳哦)
1、浅层
def h(): print('Wen Chuan') yield 5 print('Fighting!') c = h() next(c)
输出:
这里的第一个next,只输出了yield之前的东西。
程序从c=h()开始,然后进入next语句,然后启动generator h(),打印第一句"Wen chuan",然后进入yield,往generator生成一个数据5,然后也没有退出generator之类的语句,所以卡在这了,如果在来一个next,则可以全部输出:
为啥没有输出5?当我们再次调用next时,会继续执行,直到找到下一个yield表达式。由于后面没有yield了,因此会拋出异常。从上面这个例子也可验证next等价于send(None)这个道理。
2、深入
1 def h(): 2 print('Wen Chuan') 3 mm = yield 5 # Fighting! 4 print("mm=",m) 5 dd = yield 12 6 print('We are together! dd=',dd) 7 8 c = h() 9 m=next(c) #相当于c.send(None) 10 d=c.send('Fighting!') #(yield 5)表达式被赋予了'Fighting!' 11 print(m,d) 12 a=next(c) 13 print('error',a)
过程:
1.c=h(),程序开始
2.m=next(c)启动生成器
3.进入生成器h(),一直扫描直到遇到yield,所以打印了‘Wen chuan’,此时遇到yield 5,将5压进生成器,因为第三行代码yield在等式右边,所以返回generator的参数,也就是5,赋值给mm(这里我总是弄混到底mm是甚麽),此时“子程序”停留在第三行
4.回到“主程序”第9行,因为将next获得的值赋给m,所以此时刚才进栈的5再出栈给m,接着运行第10行(send作用和next相似,但是send可以传递yield表达式的值,如果h()是需要传参的,就用send传)因为是send,所以又去“子程序”运行,原本“子程序”运行到第三行,将send携带的参数传递给等式左边,即mm=Fighting!,接着第四行打印mm,接着运行,遇到yield 12,将12压进栈,此时“子程序”停留在第5行等式右边
5.回到“主程序”第10行,send从生成器出栈12复制给d,接着运行11行打印5,12,然后12行next又去“子程序”找yield,回到子程序第5行,因为next不能带参数,所以这个yield表达式没有值,所以dd=None,然后打印第6行,然后h()已经运行完毕,没有找到yield,报错,然后就不会返回到“主程序了”,接下来的13行也没有打印。
3、理解
1)
1 def h(): 2 print('Wen Chuan') 3 mm = yield 5 # Fighting! 4 print("mm=",m) 5 dd = yield 12 6 print('We are together! dd=',dd) 7 8 c = h() 9 m=next(c) #相当于c.send(None) 10 d=c.send('Fighting!') #(yield 5)表达式被赋予了'Fighting!' 11 print(m,d) 12 13 print('error')
这里,没有报错,而且执行了13行没有执行第6行:在第10行send之后执行到“子程序”中的第5行,就不往下执行了,然后返回到第10行,接着把“主程序”执行完。而上一个例子报错是又有一个next,所以“子程序”接着往下找把“子程序”找完了也没找到yield,所以第6行执行了,而13行不执行,相当于在“主程序”中第12行遇到一个error直接退出本层循环所以退出运行,所以第13行就没有执行。
2)
这里类似“主程序”“子程序”或者多线程的理解来理解协程,但是协程与多线程相比不需要线程切换的开销,与子程序比起来协程可以“子程序”内部中断,再去执行别的“子程序”,有点类似CPU中断。
上面还有一个问题是为甚麽没有输出yield的5,通过第二个例子的理解,也可看到,第一个例子只有next语句,并不是b=next(c),所以是有返回值5的,只是没有接收。在这里要特别注意b=yield 5,b到底是多少,b的值是send传进来的,而5这个值又被传回给bb=send中的bb。
3)
有点全局变量和局部变量的感觉,可以在“子程序”中调用“主程序”的m,但是在“主程序”中不能调用“子程序”的mm,我的理解是h()是一个generator,只有通过yield的数据才能放在这个generator中,同样,要调用的数据也必须是yield过的,这里的mm并不是yield的数据,所以在“主程序”中显示没有这个mm变量
1 def h(): 2 print('Wen Chuan') 3 mm = yield 5 4 print("mm=",m,mm)#可以调用m 5 dd = yield 12 6 print('We are together! dd=',dd) 7 8 c = h() 9 m=next(c) 10 d=c.send('Fighting!') 11 print(m,d) 12 13 print('error',mm)#不能调用mm
4、关于廖老师网站那个题目的运行过程的理解
1 def consumer(): 2 r = '' 3 index=1 4 while True: 5 n = yield r 6 if not n: 7 return 8 print('[CONSUMER] Consuming %s...<index=%s>' % (n,index)) 9 r = '200 OK' 10 index+=2 11 12 def produce(c): 13 c.send(None) 14 n = 0 15 while n < 5: 16 n = n + 1 17 print('[PRODUCER] Producing %s...' % n) 18 r = c.send(n) 19 print('[PRODUCER] Consumer return: %s' % r) 20 c.close() 21 22 c = consumer() 23 produce(c)
1.第22行开始运行程序,第23行produce(c),进入produce函数
2.进入produce函数,第13行预激generator,进入consumer函数,直到运行到第5行,此时consumer中的这个n值还没有被赋值,yield栈中为‘ ’,再返回produce函数第13行,因为无赋值,接着进入后面
3.第17行输出Producing 1...,然后第18行将produce的n=1传给consumer,跳转到第5行,将1赋值给consumer中的这个n,接着往下执行,打印Consuming 2...,然后r='200 ok',再进入while true,运行到第5行,此时将r='200 ok'yield进栈,返回到produce函数
4.返回到produce函数第18行,send取出当前栈中的‘200 ok’赋值给produce这里的r,然后打印Consumeing return :200 ok,然后进入while n<5:接下来循环进行。
5.直到最后一次,consumer此时在第5行,yield进栈‘200 ok',此时n还没赋值,返回produce函数第18行,然后打印Consumeing return :200 ok,然后退出while n<5循环,然后这里不知道是怎末退出consumer的,从consumer来说,接下来因为第5行没有收到新的数据,所以这里的n值为None,通过if not n可以退出,然后返回到produce第20行,手动关闭生生成器,退出整个程序。然鹅我删掉了close()和if not n,还是可以正常退出。还等学习更多相关知识应该可以彻彻底底的理解协程整个过程吧!
额外:这里设了一个index可以看到每次produce和consumer互相转换的时候,这个index不受影响,但是在consumer以外的环境调用不了index,这一点比起局部变量来说要舒服多了。