Blog Email GitHub

20 Dec 2010
Thinking in yield

yield作为python的一个关键字,如果在函数体使用,那么这个调用这个函数返回一个“生成迭代器”(generator iterator),当然在平时都称呼为“生成器”。 为了更深入的了解生成器,还是先介绍下生成器有的特性和特点吧:

  • 可以迭代(恩,这个特性是废话,从名字都可以看出来,但是这的确是生成器最基本的一个特性)
  • 通过调用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开始,初始化代码:

    /* An explanation is in order for the next line.

       f->f_lasti now refers to the index of the last instruction
       executed.  You might think this was obvious from the name, but
       this wasn't always true before 2.3!  PyFrame_New now sets
       f->f_lasti to -1 (i.e. the index *before* the first instruction)
       and YIELD_VALUE doesn't fiddle with f_lasti any more.  So this
       does work.  Promise. */
    next_instr = first_instr + f->f_lasti + 1;
    stack_pointer = f->f_stacktop;
    assert(stack_pointer != NULL);
    f->f_stacktop = NULL;   /* remains NULL unless yield suspends frame */

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

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

Resouces & References: