Thinking in yield

yield作为python的一个关键字,如果在函数体使用,那么这个调用这个函数返回一个“生成迭代器”(generator iterator),当然在平时都称呼为“生成器”。xrange函数就是我们常见的调用后返回生成器(当然这个是已经经过包装的生成器),xrange与range的区别是什么?常见的回答当然是:xrange省内存,在遍历的时候需要的时候才会生成所需要的时,而不像range,首先把所有的值都生成好保存在一个list中。恩,这的确是他们的最表面的区别。

为了更深入的了解生成器,还是先介绍下生成器有的特性和特点吧:

  • 可以迭代(恩,这个特性是废话,从名字都可以看出来,但是这的确是生成器最基本的一个特性)
  • 通过调用next函数或者send函数(其实next() = send(None) ),会执行到yield语句,就会被冻结,冻结后就返回它的caller;直至调用下次send函数从上次冻结点接着执行
  • 当生成器对象引用计数为0被回收时,如果发现生成器对象仍然被冻结,就会调用close函数,close函数的作用就是抛出一个GeneratorExit的异常
  • 生成器只能被迭代一次(有点惊讶?我看源码的时候,发现这个的时候也有点)

那generator是如何实现的呢?看了源码其实很简单,在初始化一个生成器对象,都需要一个参数: PyFrameObject * f。这个f就是生成器函数的栈帧,当函数被冻结时,记录这个栈帧的栈点stacktop 以及虚拟机字节码位置f_lasti。下次执行的时候直接从这个字节码位置和栈点执行。一切很简单吧!

一切谜底都要从python源码Python/ceval.c中的PyEval_EvalFrameEx开始,初始化代码:

  1. /* An explanation is in order for the next line. 
  2.    f->f_lasti now refers to the index of the last instruction 
  3.    executed.  You might think this was obvious from the name, but 
  4.    this wasn't always true before 2.3!  PyFrame_New now sets 
  5.    f->f_lasti to -1 (i.e. the index *before* the first instruction) 
  6.    and YIELD_VALUE doesn't fiddle with f_lasti any more.  So this 
  7.    does work.  Promise. */  
  8. next_instr = first_instr + f->f_lasti + 1;  
  9. stack_pointer = f->f_stacktop;  
  10. assert(stack_pointer != NULL);  
  11. f->f_stacktop = NULL;  
  12. /* remains NULL unless yield suspends frame */  

那yield或者说生成器都有哪些应用呢:

  • 生成迭代器(靠,又是废话)
  • 与with_statement结合使用
  • 著名的”Trampoline in python”
  • 轻量级任务
  • 其他……

Resouces & References:

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。