python enhanced generator - coroutine

  本文主要介绍python中Enhanced generator即coroutine相关内容,包括基本语法、使用场景、注意事项,以及与其他语言协程实现的异同。

enhanced generator

  在上文介绍了yield和generator的使用场景和主意事项,只用到了generator的next方法,事实上generator还有更强大的功能。PEP 342为generator增加了一系列方法来使得generator更像一个协程Coroutine。做主要的变化在于早期的yield只能返回值(作为数据的产生者), 而新增加的send方法能在generator恢复的时候消费一个数值,而去caller(generator的调用着)也可以通过throw在generator挂起的主动抛出异常。

  首先看看增强版本的yield,语法格式如下:
  back_data = yield cur_ret

  这段代码的意思是:当执行到这条语句时,返回cur_ret给调用者;并且当generator通过next()或者send(some_data)方法恢复的时候,将some_data赋值给back_data.For example:

 1 def gen(data):
 2     print ‘before yield‘, data
 3     back_data = yield data
 4     print ‘after resume‘, back_data
 5
 6 if __name__ == ‘__main__‘:
 7     g = gen(1)
 8     print g.next()
 9     try:
10         g.send(0)
11     except StopIteration:
12         pass

输出:
before yield 1
1
after resume 0

两点需要注意

(1) next() 等价于 send(None)
(2) 第一次调用时,需要使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有Python yield语句来接收这个值。

应用场景

  当generator可以接受数据(在从挂起状态恢复的时候) 而不仅仅是返回数据时, generator就有了消费数据(push)的能力。下面的例子来自这里:

 1 word_map = {}
 2 def consume_data_from_file(file_name, consumer):
 3     for line in file(file_name):
 4         consumer.send(line)
 5
 6 def consume_words(consumer):
 7     while True:
 8         line = yield
 9         for word in (w for w in line.split() if w.strip()):
10             consumer.send(word)
11
12 def count_words_consumer():
13     while True:
14         word  = yield
15         if word not in word_map:
16             word_map[word] = 0
17         word_map[word] += 1
18     print word_map
19
20 if __name__ == ‘__main__‘:
21     cons = count_words_consumer()
22     cons.next()
23     cons_inner = consume_words(cons)
24     cons_inner.next()
25     c = consume_data_from_file(‘test.txt‘, cons_inner)
26     print word_map

  上面的代码中,真正的数据消费者是count_words_consumer,最原始的数据生产者是consume_data_from_file,数据的流向是主动从生产者推向消费者。不过上面第22、24行分别调用了两次next,这个可以使用一个decorator封装一下。

1 def consumer(func):
2     def wrapper(*args,**kw):
3         gen = func(*args, **kw)
4         gen.next()
5         return gen
6     wrapper.__name__ = func.__name__
7     wrapper.__dict__ = func.__dict__
8     wrapper.__doc__  = func.__doc__
9     return wrapper

修改后的代码

 example_with_deco

generator throw

  除了next和send方法,generator还提供了两个实用的方法,throw和close,这两个方法加强了caller对generator的控制。send方法可以传递一个值给generator,throw方法在generator挂起的地方抛出异常,close方法让generator正常结束(之后就不能再调用next send了)。下面详细介绍一下throw方法。

throw(type[, value[, traceback]]) 
  在generator yield的地方抛出type类型的异常,并且返回下一个被yield的值。如果type类型的异常没有被捕获,那么会被传给caller。另外,如果generator不能yield新的值,那么向caller抛出StopIteration异常

 1 @consumer
 2 def gen_throw():
 3     value = yield
 4     try:
 5         yield value
 6     except Exception, e:
 7         yield str(e) # 如果注释掉这行,那么会抛出StopIteration
 8
 9 if __name__ == ‘__main__‘:
10     g = gen_throw()
11     assert g.send(5) == 5
12     assert g.throw(Exception, ‘throw Exception‘) == ‘throw Exception‘

  第一次调用send,代码返回value(5)之后在第5行挂起, 然后generator throw之后会被第6行catch住。如果第7行没有重新yield,那么会重新抛出StopIteration异常。

 

注意事项

  如果一个生成器已经通过send开始执行,那么在其再次yield之前,是不能从其他生成器再次调度到该生成器

 1 @consumer
 2 def funcA():
 3     while True:
 4         data = yield
 5         print ‘funcA recevie‘, data
 6         fb.send(data * 2)
 7
 8 @consumer
 9 def funcB():
10     while True:
11         data = yield
12         print ‘funcB recevie‘, data
13         fa.send(data * 2)
14
15 fa = funcA()
16 fb = funcB()
17 if __name__ == ‘__main__‘:
18     fa.send(10)

输出:

funcA recevie 10
funcB recevie 20
ValueError: generator already executing

Generator 与 Coroutine

  回到Coroutine,可参见维基百科解释(https://en.wikipedia.org/wiki/Coroutine#Implementations_for_Python),而我自己的理解比较简单(或者片面):程序员可控制的并发流程,不管是进程还是线程,其切换都是操作系统在调度,而对于协程,程序员可以控制什么时候切换出去,什么时候切换回来。协程比进程 线程轻量级很多,较少了上下文切换的开销。另外,由于是程序员控制调度,一定程度上也能避免一个任务被中途中断.。协程可以用在哪些场景呢,我觉得可以归纳为非阻塞等待的场景,如游戏编程,异步IO,事件驱动。

  Python中,generator的send和throw方法使得generator很像一个协程(coroutine), 但是generator只是一个半协程(semicoroutines),python doc是这样描述的:

  “All of this makes generator functions quite similar to coroutines; they yield multiple times, they have more than one entry point and their execution can be suspended. The only difference is that a generator function cannot control where should the execution continue after it yields; the control is always transferred to the generator’s caller.

  尽管如此,利用enhanced generator也能实现更强大的功能。比如上文中提到的yield_dec的例子,只能被动的等待时间到达之后继续执行。在某些情况下比如触发了某个事件,我们希望立即恢复执行流程,而且我们也关心具体是什么事件,这个时候就需要在generator send了。另外一种情形,我们需要终止这个执行流程,那么刻意调用close,同时在代码里面做一些处理,伪代码如下:

1 @yield_dec
2 def do(a):
3     print ‘do‘, a
4     try:
5         event = yield 5
6         print ‘post_do‘, a, event
7     finally:
8         print ‘do sth‘

  至于之前提到的另一个例子,服务(进程)之间的异步调用,也是非常适合实用协程的例子。callback的方式会割裂代码,把一段逻辑分散到多个函数,协程的方式会好很多,至少对于代码阅读而言。其他语言,比如C#、Go语言,协程都是标准实现,特别对于go语言,协程是高并发的基石。在python3.x中,通过asyncio和async\await也增加了对协程的支持。在笔者所使用的2.7环境下,也可以使用greenlet,之后会有博文介绍。

References:

https://www.python.org/dev/peps/pep-0342/
http://www.dabeaz.com/coroutines/
https://en.wikipedia.org/wiki/Coroutine#Implementations_for_Python

时间: 2024-10-12 22:21:35

python enhanced generator - coroutine的相关文章

Python高级编程之生成器(Generator)与coroutine(四):一个简单的多任务系统

啊,终于要把这一个系列写完整了,好高兴啊 在前面的三篇文章中介绍了Python的Python的Generator和coroutine(协程)相关的编程技术,接下来这篇文章会用Python的coroutine技术实现一个简单的多任务的操作系统 代码如下,可看注释 1 #-*-coding:utf-8 -*- 2 ''' 3 用Python和coroutine实现一个简单的多任务系统 4 ''' 5 # ##Step 1:Define Tasks###########################

Python高级编程之生成器(Generator)与coroutine(一):Generator

这是一系列的文章,会从基础开始一步步的介绍Python中的Generator以及coroutine(协程)(主要是介绍coroutine),并且详细的讲述了Python中coroutine的各种高级用法,最后会用coroutine实现一个简单的多任务的操作系统. 其实也是看完这篇文章的学习笔记吧!O(∩_∩)O 生成器(Generator) 什么是生成器?在Python中,生成器(Generator)是一个带有yield关键字的函数 1 def gene(): 2 a = 1 3 print "

Python高级编程之生成器(Generator)与coroutine(二):coroutine与pipeline(管道)和Dataflow(数据流_

原创作品,转载请注明出处:点我 在前两篇文章中,我们介绍了什么是Generator和coroutine,在这一篇文章中,我们会介绍coroutine在模拟pipeline(管道 )和控制Dataflow(数据流)方面的运用. coroutine可以用来模拟pipeline行为.通过把多个coroutine串联在一起来实现pipe,在这个管道中,数据是通过send()函数在各个coroutine之间传递的: 但是这些在pipe中传递的数据哪里来的呢?这就需要一个数据源,或者说producer.这个

Python高级编程之生成器(Generator)与coroutine(二):coroutine介绍

原创作品,转载请注明出处:点我 上一篇文章Python高级编程之生成器(Generator)与coroutine(一):Generator中,我们介绍了什么是Generator,以及写了几个使用Generator Function的示例,这一小节,我们会介绍Python的coroutine,以及会有一个小例子,再接下来的文章中会以代码的形式一步步介绍coroutine的高级用法. coroutine(协程) 什么是coroutine?coroutine跟Generator有什么区别?下面先看一段

python yield generator 详解

正文 本文将由浅入深详细介绍yield以及generator,包括以下内容:什么generator,生成generator的方法,generator的特点,generator基础及高级应用场景,generator使用中的注意事项.本文不包括enhanced generator即pep342相关内容,这部分内容在之后的博文介绍. generator基础 回到顶部 在python的函数(function)定义中,只要出现了yield表达式(Yield expression),那么事实上定义的是一个g

01-Python学习笔记-基础语法

Python标识符 -d           在解析时显示调试信息 -O           生成优化代码 ( .pyo 文件 ) -S           启动时不引入查找Python路径的位置 -v            输出Python版本号 -X           从 1.6版本之后基于内建的异常(仅仅用于字符串)已过时. -c cmd     执行 Python 脚本,并将运行结果作为 cmd 字符串. file           在给定的python文件执行python脚本. P

python -- decorator && generator && iterator

python -- decorator && generator && iterator 一. decorator 1. 2. 二. generator 三. iterator 原文地址:https://www.cnblogs.com/-cjzsr-/p/8167923.html

python yield用法 (tornado, coroutine)

yield关键字用来定义生成器(Generator),其具体功能是可以当return使用,从函数里返回一个值,不同之处是用yield返回之后,可以让函数从上回yield返回的地点继续执行.也就是说,yield返回函数,交给调用者一个返回值,然后再“瞬移”回去,让函数继续运行, 直到吓一跳yield语句再返回一个新的值. 使用yield返回后,调用者实际得到的是一个迭代器对象,迭代器的值就是返回值,而调用该迭代器的next()方法会导致该函数恢复yield语句的执行环境继续往下跑,直到遇到下一个y

python 生成器 generator

一.生成器定义 通过列表生成表达式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间.在Python中,这种一边循环一边计算的机制,称为生成器:generator. 1 >>> l = [x * x for x in range(10)] 2 >>> l 3 [0, 1, 4, 9, 16, 25, 36,