<code>yield</code>的功能类似于<code>return</code>,但是不同之处在于它返回的是<code>生成器</code>。
生成器是通过一个或多个<code>yield</code>表达式构成的函数,每一个生成器都是一个迭代器(但是迭代器不一定是生成器)。
如果一个函数包含<code>yield</code>关键字,这个函数就会变为一个生成器。
生成器并不会一次返回所有结果,而是每次遇到<code>yield</code>关键字后返回相应结果,并保留函数当前的运行状态,等待下一次的调用。
由于生成器也是一个迭代器,那么它就应该支持<code>next</code>方法来获取下一个值。
除了<code>next</code>函数,生成器还支持<code>send</code>函数。该函数可以向生成器传递参数。
最经典的例子,生成无限序列。
常规的解决方法是,生成一个满足要求的很大的列表,这个列表需要保存在内存中,很明显内存限制了这个问题。
如果使用生成器就不需要返回整个列表,每次都只是返回一个数据,避免了内存的限制问题。
生成器的源码在<code>objects/genobject.c</code>。
在解释生成器之前,需要讲解一下python虚拟机的调用原理。
python虚拟机有一个栈帧的调用栈,其中栈帧的是<code>pyframeobject</code>,位于<code>include/frameobject.h</code>。
栈帧保存了给出代码的的信息和上下文,其中包含最后执行的指令,全局和局部命名空间,异常状态等信息。<code>f_valueblock</code>保存了数据,<code>b_blockstack</code>保存了异常和循环控制方法。
举一个例子来说明,
那么,相应的调用栈如下,一个py文件,一个类,一个函数都是一个代码块,对应者一个frame,保存着上下文环境以及字节码指令。
每一个栈帧都拥有自己的数据栈和block栈,独立的数据栈和block栈使得解释器可以中断和恢复栈帧(生成器正式利用这点)。
python代码首先被编译为字节码,再由python虚拟机来执行。一般来说,一条python语句对应着多条字节码(由于每条字节码对应着一条c语句,而不是一个机器指令,所以不能按照字节码的数量来判断代码性能)。
调用<code>dis</code>模块可以分析字节码,
其中,
由了上面对于调用栈的理解,就可以很容易的明白生成器的具体实现。
生成器的源码位于<code>object/genobject.c</code>。
<code>next</code>与<code>send</code>函数,如下
从上面的代码中可以看到,<code>send</code>和<code>next</code>都是调用的同一函数<code>gen_send_ex</code>,区别在于是否带有参数。
<code>pyeval_evalframeex</code>函数的功能为执行字节码并返回结果。
举一个例子,<code>f_back</code>上一个frame,<code>f_lasti</code>上一次执行的指令的偏移量,
结果如下,其中第三行的英文为操作码,对应着上面的<code>opcode</code>,每次switch都是在不同的<code>opcode</code>之间进行选择。
![]()
Python yield与实现