生成器如何在python中工作

use*_*562 8 python generator yield-keyword

我是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? 
 ] 
Run Code Online (Sandbox Code Playgroud)

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

我的问题:

  1. Python内部用于存储状态的内容是什么?

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

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

dan*_*ano 15

任何包含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; 
Run Code Online (Sandbox Code Playgroud)

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

PyObject*PyEval_EvalFrameEx(PyFrameObject*f,int throwflag)

这是Python解释的主要功能.它实际上是2000行.执行与执行帧f相关联的代码对象,解释字节码并根据需要执行调用.额外的throwflag参数大多可以忽略 - 如果为true,则会导致立即抛出异常; 这用于生成器对象的throw()方法.

它知道当它yield在评估字节码时遇到一段时间,它应该返回给调用者的值:

    TARGET(YIELD_VALUE) {
        retval = POP();
        f->f_stacktop = stack_pointer;
        why = WHY_YIELD;
        goto fast_yield;
    }
Run Code Online (Sandbox Code Playgroud)

当你屈服时,帧的值栈的当前值被保持(通过f->f_stacktop = stack_pointer),这样我们就可以在next再次调用时从中断处继续.所有非生成器函数在完成评估后设置f_stacktopNULL.因此,当您next再次调用生成器对象时,PyEval_ExvalFrameEx将再次调用,使用与之前相同的帧指针.指针的状态将与之前产生的状态完全相同,因此执行将从该点继续执行.本质上,帧的当前状态是"冻结的".引入生成器PEP中描述了这一点:

如果遇到yield语句,则冻结函数的状态,并将值[yielding]返回给.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;
Run Code Online (Sandbox Code Playgroud)

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

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