本文主要介绍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
example_with_deco

相关文章: