9

我是 Python 和编程的新手。对于新程序员来说,生成器有点太复杂了。这是我关于 Python 中生成器函数的理论:

  1. 任何包含yield语句的函数都会返回一个生成器对象

  2. 生成器对象是包含状态的堆栈

  3. 每次我调用.next方法时,Python 都会提取函数的状态,当它找到另一个 yield 语句时,它会再次绑定状态并删除先前的状态:

例子:

 [ 
  [state1] # Stack contains states and states contain info about the function
  [state2] # State1 will be deleted when python finds the other yield? 
 ] 

这当然可能是地球上最愚蠢的理论,但请原谅我只是编码词的新手。

我的问题:

  1. Python 在内部做什么来存储状态?

  2. yield如果存在,语句是否将状态添加到堆栈?

  3. 内部创造了什么收益?我知道 yield 创建了一个生成器对象,但是,我想知道生成器对象包含哪些使它们工作的东西?它们只是状态的堆栈/列表,我们使用.next方法来提取每个状态,Python 会自动调用具有索引状态的函数吗?

4

1 回答 1

16

任何包含 yield 语句的函数都会返回一个生成器对象

这是对的。包含 a 的函数的返回值yield是一个生成器对象。生成器对象是一个迭代器,其中每次迭代都返回一个yield从支持生成器的代码编辑的值。

生成器对象是包含状态的堆栈

生成器对象包含指向当前执行帧的指针,以及用于维护生成器状态的一大堆其他内容。执行框架包含生成器中代码的调用堆栈。

每次我调用 .next 方法时,Python 都会提取函数的状态,当它找到另一个 yield 语句时,它会再次绑定状态并删除先前的状态

有点。当您调用时next(gen_object),Python会评估当前的执行帧

gen_send_ex(PyGenObject *gen, PyObject *arg, int exc) {  // This is called when you call next(gen_object)
    PyFrameObject *f = gen->gi_frame;
    ...
    gen->gi_running = 1;
    result = PyEval_EvalFrameEx(f, exc);  // This evaluates the current frame
    gen->gi_running = 0; 

PyEval_EvalFrame是用于解释 Python 字节码的最高级别函数:

PyObject* PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)

这是 Python 解释的主要、朴素的功能。它实际上是 2000 行长。执行与执行帧 f 关联的代码对象,根据需要解释字节码并执行调用。附加的 throwflag 参数大多可以忽略 - 如果为 true,则立即引发异常;this 用于生成器对象的 throw() 方法。

它知道当它yield评估字节码时,它应该将产生的值返回给调用者

    TARGET(YIELD_VALUE) {
        retval = POP();
        f->f_stacktop = stack_pointer;
        why = WHY_YIELD;
        goto fast_yield;
    }

当您让出时,帧的值堆栈的当前值被保持(通过f->f_stacktop = stack_pointer),以便我们可以在next再次调用时从中断的地方继续。所有非生成器函数在完成评估后设置f_stacktop为。NULL因此,当您next再次调用生成器对象时,PyEval_ExvalFrameEx会再次调用,使用与以前相同的帧指针。指针的状态将与前一次产生时完全相同,因此将从该点继续执行。本质上,框架的当前状态是“冻结的”。这在引入生成器的 PEP 中有所描述:

如果遇到 yield 语句,函数的状态将被冻结,并将值 [yielded] 返回给 .next() 的调用者。“冻结”是指保留所有局部状态,包括局部变量的当前绑定、指令指针和内部评估堆栈:保存了足够的信息,以便下次调用 .next() 时,函数可以就像 yield 语句只是另一个外部调用一样。

以下是生成器对象维护的大部分状态(直接取自其头文件):

typedef struct {
    PyObject_HEAD
    /* The gi_ prefix is intended to remind of generator-iterator. */

    /* Note: gi_frame can be NULL if the generator is "finished" */
    struct _frame *gi_frame;

    /* True if generator is being executed. */
    char gi_running;

    /* The code object backing the generator */
    PyObject *gi_code;

    /* List of weak reference. */
    PyObject *gi_weakreflist;

    /* Name of the generator. */
    PyObject *gi_name;

    /* Qualified name of the generator. */
    PyObject *gi_qualname;
} PyGenObject;

gi_frame是指向当前执行帧的指针。

请注意,所有这些都是特定于 CPython 实现的。PyPy/Jython/等。很可能以完全不同的方式实现生成器。我鼓励您通读生成器对象的源代码,以了解有关 CPython 实现的更多信息。

于 2014-08-10T21:14:45.003 回答