本文主要介绍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
修改后的代码:
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 10 11 word_map = {} 12 def consume_data_from_file(file_name, consumer): 13 for line in file(file_name): 14 consumer.send(line) 15 16 @consumer 17 def consume_words(consumer): 18 while True: 19 line = yield 20 for word in (w for w in line.split() if w.strip()): 21 consumer.send(word) 22 23 @consumer 24 def count_words_consumer(): 25 while True: 26 word = yield 27 if word not in word_map: 28 word_map[word] = 0 29 word_map[word] += 1 30 print word_map 31 32 if __name__ == '__main__': 33 cons = count_words_consumer() 34 cons_inner = consume_words(cons) 35 c = consume_data_from_file('test.txt', cons_inner) 36 print word_map