欢迎加入QQ讨论群258996829
麦子学院 头像
苹果6袋
6
麦子学院

Python学习之Yield与 Generator

发布时间:2017-07-26 09:30  回复:0  查看:2149   最后回复:2017-07-26 09:30  
本文将由浅入深详细介绍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
来源: 伯乐在线
您还未登录,请先登录

热门帖子

最新帖子