Python中的生成器是如何工作的
我刚开始学Python和编程,对生成器的理解有点困难。以下是我对Python中生成器函数的看法:
任何包含
yield
语句的函数都会返回一个生成器对象。生成器对象就像一个包含状态的堆栈。
每次我调用
.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?
]
当然,这可能是世界上最愚蠢的理论,但请原谅我,我只是刚入门编程。
我有几个问题:
Python内部是如何存储这些状态的?
如果已经存在,
yield
语句会将一个状态添加到堆栈中吗?yield
内部创建了什么?我知道yield
会创建一个生成器对象,但我想知道生成器对象内部包含了什么,使它们能够工作?它们只是一个状态的堆栈/列表吗?我们使用.next
方法提取每个状态,Python会自动调用带有索引状态的函数吗?
1 个回答
任何包含yield语句的函数都会返回一个生成器对象。
这句话是对的。包含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参数大多数情况下可以忽略——如果为真,它会立即抛出一个异常;这个参数用于生成器对象的throw()方法。
当它在评估字节码时遇到yield
,就会把正在yield
的值返回给调用者:
TARGET(YIELD_VALUE) {
retval = POP();
f->f_stacktop = stack_pointer;
why = WHY_YIELD;
goto fast_yield;
}
当你使用yield
时,当前执行状态的值会被保留(通过f->f_stacktop = stack_pointer
),这样当再次调用next
时,就能从上次停止的地方继续。所有非生成器函数在完成评估后会把f_stacktop
设置为NULL
。所以当你再次调用生成器对象的next
时,PyEval_ExvalFrameEx
会再次被调用,使用之前的状态指针。这个指针的状态和上次yield
时完全一样,因此执行会从那个点继续。实际上,当前的执行状态是“冻结”的。这在引入生成器的PEP中有描述:
如果遇到一个yield语句,函数的状态就会被冻结,返回的值会传递给
.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实现的内容。