本文主要介绍python中Enhancedgenerator即协程的相关内容,包括基本语法、使用场景、注意事项以及与其他语言协程实现的异同。增强生成器在上面的《Python Yield Generator 详解》介绍了yield和generator的使用场景和思路。只使用生成器的next方法。其实生成器还有更强大的功能。PEP342为生成器添加了一系列方法,使生成器更像协程。主要变化是早期的yield只能返回一个值(作为数据的生成者),新增加的send方法在生成器恢复时可以消费一个值,调用者(生成器的调用者)也可以通过throwin生成器抛出的活动异常被挂起。back_data=yieldcur_ret这段代码的意思是:当这条语句执行时,将cur_ret返回给调用者;当生成器通过next()或send(some_data)方法恢复时,将some_data赋值给back_data。例如:defgen(data):print'beforeyield',databack_data=yielddataprint'afterresume',back_dataif__name__=='__main__':g=gen(1)printg.next()try:g.send(0)exceptStopIteration:pass输出:beforeyield11afterresume0两点注意:next()等同于send(None)。第一次调用时,需要使用next()语句或send(None)。不能使用send发送None以外的值,否则会出现错误,因为没有Pythonyield语句来接收这个值。应用场景当生成器可以接受数据(当从挂起状态恢复时)而不仅仅是返回数据时,生成器具有消费数据(推送)的能力。以下示例来自此处:word_map={}defconsume_data_from_file(file_name,consumer):forlineinfile(file_name):consumer.send(line)defconsume_words(consumer):whileTrue:line=yieldforwordin(wforwinline.split()ifw.strip()):consumer.send(word)defcount_words_consumer():whileTrue:word=yieldifwordnotinword_map:word_map[word]=0word_map[word]+=1printword_mapif__name__=='__main__':cons=count_words_consumer()cons.next()cons_inner=consume_words(cons)cons_inner.next()c=consume_data_from_file('test.txt',cons_inner)上面printword_map的代码中,真正的数据消费者是count_words_consumer,最原始的数据生产者是consume_data_from_file,数据的流向是主动推送的生产者给消费者。不过上面22行和24行调用了两次next,可以用装饰器封装一下。defconsumer(func):defwrapper(*args,**kw):gen=func(*args,**kw)gen.next()返回genwrapper.__name__=func.__name__wrapper.__dict__=func.__dict__wrapper.__doc__=func。__doc__returnwrapper修改后的代码:defconsumer(func):defwrapper(*args,**kw):gen=func(*args,**kw)gen.next()returngenwrapper.__name__=func.__name__wrapper.__dict__=func.__dict__wrapper.__doc__=func.__doc__returnwrapperword_map={}defconsume_data_from_file(file_name,consumer):forlineinfile(file_name):consumer.send(line)@consumerdefconsume_words(consumer):whileTrue:line=yieldforwordin(wforwinline.split()ifw.strip()):consumer.send(word)@consumerdefcount_words_consumer():whileTrue:word=yieldifwordnotinword_map:word_map[word]=0word_map[word]+=1printword_mapif__name__=='__main__':cons=count_words_consumer()cons_inner=consume_words(cons)c=consume_data_from_file('test.txt',cons_inner)printword_mapexample_with_decogeneratorthrow除了next和send方法,生成器还提供了throw和close两个实用的方法,加强了调用者对生成器的控制。send方法可以给generator传一个值,throw方法在generator挂起的地方抛出异常,close方法可以让generator正常结束(之后不能调用下一个send)。下面详细介绍throw方法。throw(type[,value[,traceback]])在生成器产生的地方抛出类型为type的异常,并返回下一个产生的值。如果类型类型的异常没有被捕捉到,就会传递给调用者。此外,如果生成器不能产生新值,则向调用者抛出StopIteration异常:@consumerdefgen_throw():value=yieldtry:yieldvalueexceptException,e:yieldstr(e)#如果这一行被注释掉,StopIterationif__name__=='__main__':g=gen_throw()assertg.send(5)==5assertg.throw(Exception,'throwException')=='throwException'第一次调用send,代码返回value(5)在线挂5,那么generatorthrow将被第6行捕获。如果第7行没有重新yield,那么StopIteration异常将被重新抛出。注意事项如果一个generator已经通过send执行过,在它再次yield之前不能从其他generator再次调度到该generator@consumerdeffuncA():whileTrue:data=yieldprint'funcArecevie',datafb.send(data*2)@consumerdeffuncB():whileTrue:data=yieldprint'funcBrecevie',datafa.send(data*2)fa=funcA()fb=funcB()if__name__=='__main__':fa.send(10)输出:funcArecevie10funcBrecevie20ValueError:generatoralreadyexecutingGeneratorandCoroutine回到Coroutine,看维基百科的解释,但是我自己的理解比较简单(或者说是片面的):程序员可以控制并发进程,不管是进程还是线程,它的切换都是由操作系统来调度的,有了协程,程序员可以控制何时切换出去,何时切换回来。协程比进程线程轻很多,减少了上下文切换的开销。另外,由于程序员控制了调度,也可以在一定程度上防止一个任务中途被打断。协程可以用在哪些场景?我觉得可以概括为非阻塞等待的场景,比如游戏编程,异步IO,事件驱动。在Python中,生成器的send和throw方法使生成器看起来像一个协程,但生成器只是一个半协程。python文档是这样描述的:“所有这些使得生成器函数与协程非常相似;它们产生多次,它们有多个入口点并且它们的执行可以暂停。唯一的区别是生成器函数无法控制在哪里如果在它产生后继续执行;控制总是转移到生成器的调用者。”这样使用增强的生成器也可以实现更强大的功能。比如上面提到的yield_dec的例子,只能被动等待时间的到来,才能继续执行。在某些情况下,比如当一个事件被触发时,我们希望立即恢复执行过程,我们也关心具体的事件。这时候我们需要在生成器中发送。还有一种情况,我们需要终止执行过程,然后特意调用close,同时在代码中做一些处理,伪代码如下:@yield_decdefdo(a):print'do',atry:event=yield5print'post_do',a,eventfinally:print'dosth'至于前面提到的另一个例子,服务(进程)之间的异步调用也是一个非常适合实际协程的例子。回调方法会将代码拆分,将一段逻辑分散到多个函数中。协程的方式会好很多,至少在代码阅读上是这样。在其他语言中,比如C#、Go语言,协程是标准实现,尤其是对于go语言,协程是高并发的基石。在python3.x中,也通过asyncio和async\await添加了对协程的支持。在笔者使用的2.7环境下,还可以使用greenlet,后面会在一篇博文中介绍。参考https://www.python.org/dev/peps/pep-0342/http://www.dabeaz.com/coroutines/https://en.wikipedia.org/wiki/Coroutine#Implementations_for_Python
