本文将由浅入深详细介绍yield 
 
以及 
 generator 
 ,包括以下内容:什么 
 generator 
 ,生成 
 generator 
 的方法, 
 generator 
 的特点, 
 generator 
 基础及高级应用场景, 
 generator 
 使用中的注意事项等等,一起来看看吧,希望对大家 
 
学习python有所帮助 
 
。 
  
 
    
 generator基础 
 
 
   在python 
 的函数( 
 function 
 )定义中,只要出现了 
 yield 
 表达式(  
 Yield expression  
 ),那么事实上定义的是一个  
 generator function  
 , 调用这个 
 generator function 
 返回值是一个  
 generator  
 。这根普通的函数调用有所区别, 
 For example 
 : 
 
 
    
 def  
 gen_generator(): 
 
 
    
 yield 1 
 
 
    
 def  
 gen_value(): 
 
 
    
 return 1 
 
 
    
 if __name__ == '__main__': 
 
 
   ret = gen_generator() 
 
 
    
 print ret, type(ret)    # 
 
 
   ret = gen_value() 
 
 
    
 print ret, type(ret)    # 1 
 
 
   从上面的代码可以看出,gen_generator 
 函数返回的是一个 
 generator 
 实例, 
 generator 
 有以下特别: 
 
 
   ·  
 遵循迭代器( 
 iterator 
 )协议,迭代器协议需要实现 
 __iter__ 
 、 
 next 
 接口 
 
 
   ·  
 能过多次进入、多次返回,能够暂停函数体中代码的执行 
 
 
   下面看一下测试代码: 
 
 
   >>>  
 def  
 gen_example(): 
 
 
   ...      
 print 'before any yield' 
 
 
   ...      
 yield 'first yield' 
 
 
   ...      
 print 'between yields' 
 
 
   ...      
 yield 'second yield' 
 
 
   ...      
 print 'no yield anymore'... >>> gen = gen_example()>>> gen.next() 
 
 
    
 
 
   #  
 第一次调用next 
 
 
   before any  
 yield'first yield'>>> gen.next() 
 
 
    
 
 
   #  
 第二次调用next 
 
 
   between yields'second yield'>>> gen.next() 
 
 
    
 
 
   #  
 第三次调用next 
 
 
   no  
 yield anymore 
 
 
   Traceback (most recent call last): 
 
 
   File "", line 1,  
 in 
 
 
   StopIteratio 
 
 
   调用gen example 
 方法并没有输出任何内容,说明函数体的代码尚未开始执行。当调用 
 generator 
 的 
 next 
 方法, 
 generator 
 会执行到 
 yield  
 表达式处,返回 
 yield 
 表达式的内容,然后暂停(挂起)在这个地方,所以第一次调用 
 next 
 打印第一句并返回 
 “first yield” 
 。  
 暂停  
 意味着方法的局部变量,指针信息,运行环境都保存起来,直到下一次调用next 
 方法恢复。第二次调用 
 next 
 之后就暂停在最后一个 
 yield 
 ,再次调用 
 next() 
 方法,则会抛出 
 StopIteration 
 异常。 
 
 
   因为for 
 语句能自动捕获 
 StopIteration 
 异常,所以 
 generator 
 (本质上是任何 
 iterator 
 )较为常用的方法是在循环中使用: 
 
 
    
 def  
 generator_example(): 
 
 
    
 yield 1 
 
 
    
 yield 2 
 
 
    
 if __name__ == '__main__': 
 
 
    
 for e  
 in generator_example(): 
 
 
    
 print e 
 
 
   # output 1 2 
 
 
   generator function 
 产生的 
 generator 
 与普通的 
 function 
 有什么区别呢? 
 
 
   (1 
 ) 
 function 
 每次都是从第一行开始运行,而 
 generator 
 从上一次 
 yield 
 开始的地方运行 
 
 
   (2 
 ) 
 function 
 调用一次返回一个(一组)值,而 
 generator 
 可以多次返回 
 
 
   (3 
 ) 
 function 
 可以被无数次重复调用,而一个 
 generator 
 实例在 
 yield 
 最后一个值 或者 
 return 
 之后就不能继续调用了 
 
 
   在函数中使用Yield 
 ,然后调用该函数是生成 
 generator 
 的一种方式。另一种常见的方式是使用 
 generator expression 
 , 
 For example 
 : 
 
 
   >>> gen = (x * x  
 for x  
 in xrange(5))>>>  
 print gen 
 
 
   <generator objectat 0x02655710> 
 
 
    
 generator应用 
 
 
    
 generator基础应用 
 
 
   为什么使用generator 
 呢,最重要的原因是可以  
 按需生成并“返回”结果  
 ,而不是一次性产生所有的返回值,况且有时候根本就不知道“ 
 所有的返回值 
 ” 
 。比如对于下面的代码: 
 
 
   RANGE_NUM = 100 
 
 
    
 for i  
 in [x*x  
 for x  
 in range(RANGE_NUM)]: #  
 第一种方法:对列表进行迭代 
 
 
   #  
 do sth  
 for example 
 
 
   print i 
 
 
    
 for i  
 in (x*x  
 for x  
 in range(RANGE_NUM)): #  
 第二种方法:对 
 generator 
 进行迭代 
 
 
   #  
 do sth  
 for example 
 
 
   print i 
 
 
   在上面的代码中,两个for 
 语句输出是一样的,代码字面上看来也就是中括号与小括号的区别。但这点区别差异是很大的,第一种方法返回值是一个列表,第二个方法返回的是一个 
 generator 
 对象。随着 
 RANGE_NUM 
 的变大,第一种方法返回的列表也越大,占用的内存也越大;但是对于第二种方法没有任何区别。 
 
 
   我们再来看一个可以“ 
 返回 
 ” 
 无穷多次的例子: 
 
 
    
 def  
 fib(): 
 
 
   a, b = 1, 1 
 
 
    
 while  
 True: 
 
 
    
 yield a 
 
 
   a, b = b, a+b 
 
 
   这个generator 
 拥有生成无数多 
 “ 
 返回值 
 ” 
 的能力,使用者可以自己决定什么时候停止迭代。 
 
 
    
 generator高级应用 
 
 
   使用场景一: 
 
 
   Generator 
 可用于产生数据流,  
 generator 
 并不立刻产生返回值,而是等到被需要的时候才会产生返回值,相当于一个主动拉取的过程 
 (pull) 
 ,比如现在有一个日志文件,每行产生一条记录,对于每一条记录,不同部门的人可能处理方式不同,但是我们可以提供一个公用的、按需生成的数据流。 
 
 
    
 def  
 gen_data_from_file(file_name): 
 
 
    
 for line  
 in file(file_name): 
 
 
    
 yield line 
 
 
    
 def  
 gen_words(line): 
 
 
    
 for word  
 in (w  
 for w  
 in line.split()  
 if w.strip()): 
 
 
    
 yield word 
 
 
    
 def  
 count_words(file_name): 
 
 
   word_map = {} 
 
 
    
 for line  
 in gen_data_from_file(file_name): 
 
 
    
 for word  
 in gen_words(line): 
 
 
    
 if word  
 not  
 in word_map: 
 
 
   word_map[word] = 0 
 
 
   word_map[word] += 1 
 
 
    
 return word_map 
 
 
    
 def  
 count_total_chars(file_name): 
 
 
   total = 0 
 
 
    
 for line  
 in gen_data_from_file(file_name): 
 
 
   total += len(line) 
 
 
    
 return total 
 
 
    
 if __name__ == '__main__': 
 
 
    
 print count_words('test.txt'), count_total_chars('test.txt') 
 
 
   上面的例子来自08 
 年的 
 PyCon 
 一个讲座。 
 gen_words gen_data_from_file 
 是数据生产者,而 
 count_words count_total_chars 
 是数据的消费者。可以看到,  
 数据只有在需要的时候去拉取的,而不是提前准备好  
 。另外gen_words 
 中  
 (w for w in line.split() if w.strip()) 
 也是产生了一个 
 generator 
 。 
 
 
   使用场景二: 
 
 
   一些编程场景中,一件事情可能需要执行一部分逻辑,然后等待一段时间、或者等待某个异步的结果、或者等待某个状态,然后继续执行另一部分逻辑。比如微服务架构中,服务A 
 执行了一段逻辑之后,去服务 
 B 
 请求一些数据,然后在服务 
 A 
 上继续执行。或者在游戏编程中,一个技能分成分多段,先执行一部分动作(效果),然后等待一段时间,然后再继续。对于这种需要等待、而又不希望阻塞的情况,我们一般使用回调( 
 callback 
 )的方式。下面举一个简单的例子: 
 
 
    
 def  
 do(a): 
 
 
    
 print 'do', a 
 
 
   CallBackMgr.callback(5,  
 lambda a = a: post_do(a)) 
 
 
    
 def  
 post_do(a): 
 
 
    
 print 'post_do', a 
 
 
   这里的CallBackMgr 
 注册了一个 
 5s 
 后的时间, 
 5s 
 之后再调用 
 lambda 
 函数,可见  
 一段逻辑被分裂到两个函数,而且还需要上下文的传递  
 (如这里的参数a 
 )。我们用 
 yield 
 来修改一下这个例子, 
 yield 
 返回值代表等待的时间。 
 
 
   @yield_dec 
 
 
    
 def  
 do(a): 
 
 
    
 print 'do', a 
 
 
    
 yield 5 
 
 
    
 print 'post_do', a 
 
 
   这里需要实现一个YieldManager 
 , 通过 
 yield_dec 
 这个 
 decrator 
 将 
 do 
 这个 
 generator 
 注册到 
 YieldManager 
 ,并在 
 5s 
 后调用 
 next 
 方法。 
 Yield 
 版本实现了和回调一样的功能,但是看起来要清晰许多。下面给出一个简单的实现以供参考: 
 
 
   # -*- coding:utf-8 -*- 
 import sys# import Timer 
 import types 
 import time 
 
 
    
 class  
 YieldManager(object): 
 
 
    
 def  
 __init__(self, tick_delta = 0.01): 
 
 
   self.generator_dict = {} 
 
 
   # self._tick_timer = Timer.addRepeatTimer(tick_delta, lambda: self.tick()) 
 
 
    
 def  
 tick(self): 
 
 
   cur = time.time() 
 
 
    
 for gene, t  
 in self.generator_dict.items(): 
 
 
    
 if cur >= t: 
 
 
   self._do_resume_genetator(gene,cur) 
 
 
    
 def  
 _do_resume_genetator(self,gene, cur ): 
 
 
    
 try: 
 
 
   self.on_generator_excute(gene, cur) 
 
 
    
 except StopIteration,e: 
 
 
   self.remove_generator(gene) 
 
 
    
 except Exception, e: 
 
 
    
 print 'unexcepet error', type(e) 
 
 
   self.remove_generator(gene) 
 
 
    
 def  
 add_generator(self, gen, deadline): 
 
 
   self.generator_dict[gen] = deadline 
 
 
    
 def  
 remove_generator(self, gene): 
 
 
    
 del self.generator_dict[gene] 
 
 
    
 def  
 on_generator_excute(self, gen, cur_time = None): 
 
 
   t = gen.next() 
 
 
   cur_time = cur_time  
 or time.time() 
 
 
   self.add_generator(gen, t + cur_time) 
 
 
   g_yield_mgr = YieldManager() 
 
 
    
 def  
 yield_dec(func): 
 
 
    
 def  
 _inner_func(*args, **kwargs): 
 
 
   gen = func(*args, **kwargs) 
 
 
    
 if type(gen)  
 is types.GeneratorType: 
 
 
   g_yield_mgr.on_generator_excute(gen) 
 
 
    
 return gen 
 
 
    
 return _inner_func 
 
 
   @yield_dec 
 def  
 do(a): 
 
 
    
 print 'do', a 
 
 
    
 yield 2.5 
 
 
    
 print 'post_do', a 
 
 
    
 yield 3 
 
 
    
 print 'post_do again', a 
 
 
    
 if __name__ == '__main__': 
 
 
   do(1) 
 
 
    
 for i  
 in range(1, 10): 
 
 
    
 print 'simulate a timer, %s seconds passed' % i 
 
 
   time.sleep(1) 
 
 
   g_yield_mgr.tick() 
 
 
    
 注意事项: 
 
 
   (1 
 ) 
 Yield 
 是不能嵌套的! 
 
 
    
 def  
 visit(data): 
 
 
    
 for elem  
 in data: 
 
 
    
 if isinstance(elem, tuple)  
 or isinstance(elem, list): 
 
 
   visit(elem) # here value retuened is generator 
 
 
    
 else: 
 
 
    
 yield elem 
 
 
    
 if __name__ == '__main__': 
 
 
    
 for e  
 in visit([1, 2, (3, 4), 5]): 
 
 
    
 print e 
 
 
   上面的代码访问嵌套序列里面的每一个元素,我们期望的输出是1 2 3 4 5 
 ,而实际输出是 
 1 2 5  
 。为什么呢,如注释所示, 
 visit 
 是一个 
 generator function 
 ,所以第 
 4 
 行返回的是 
 generator object 
 ,而代码也没这个 
 generator 
 实例迭代。那么改改代码,对这个临时的 
 generator  
 进行迭代就行了。 
 
 
    
 def  
 visit(data): 
 
 
    
 for elem  
 in data: 
 
 
    
 if isinstance(elem, tuple)  
 or isinstance(elem, list): 
 
 
    
 for e  
 in visit(elem): 
 
 
    
 yield e 
 
 
    
 else: 
 
 
    
 yield elem 
 
 
   或者在python3.3 
 中 可以使用 
 yield from 
 ,这个语法是在  
 pep380  
 加入的: 
 
 
    
 def  
 visit(data): 
 
 
    
 for elem  
 in data: 
 
 
    
 if isinstance(elem, tuple)  
 or isinstance(elem, list): 
 
 
    
 yield  
 from visit(elem) 
 
 
    
 else: 
 
 
    
 yield elem 
 
 
   (2 
 ) 
 generator function 
 中使用 
 return 
 
 
   在python doc 
 中,明确提到是可以使用 
 return 
 的,当 
 generator 
 执行到这里的时候抛出  
 StopIteration  
 异常。 
 
 
    
 def  
 gen_with_return(range_num): 
 
 
    
 if range_num < 0: 
 
 
    
 return 
 
 
    
 else: 
 
 
    
 for i  
 in xrange(range_num): 
 
 
    
 yield i 
 
 
    
 if __name__ == '__main__': 
 
 
    
 print list(gen_with_return(-1)) 
 
 
    
 print list(gen_with_return(1)) 
 
 
   但是,generator function 
 中的 
 return 
 是不能带任何返回值的。 
 
 
    
 def  
 gen_with_return(range_num): 
 
 
    
 if range_num < 0: 
 
 
    
 return 0 
 
 
    
 else: 
 
 
    
 for i  
 in xrange(range_num): 
 
 
    
 yield i 
 
 
   上面的代码会报错: SyntaxError: ‘return’ with argument inside generator 
 
 
 
 
 
  来源: 
 伯乐在线