基本操作
通过yield
来创建生成器
def func():
for i in xrange(10);
yield i
通过列表来创建生成器
[i for i in xrange(10)]
调用如下
f = func()
f # 此时生成器还没有运行
f.next() # 当i=0时,遇到yield关键字,直接返回
0
f.next() # 继续上一次执行的位置,进入下一层循环
1
...
f.next()
9
f.next() # 当执行完最后一次循环后,结束yield语句,生成StopIteration异常
Traceback (most recent call last):
File "", line 1, in
StopIteration除了next函数,生成器还支持send函数。该函数可以向生成器传递参数。
def func():
... n = 0
... while 1:
... n = yield n #可以通过send函数向n赋值
//代码效果参考:https://v.youku.com/v_show/id_XNjQwMDM2NzA4NA==.html
...
f = func()
f.next() # 默认情况下n为0
0
f.send(1) #n赋值1
1
f.send(2)
2应用
最经典的例子,生成无限序列。
常规的解决方法是,生成一个满足要求的很大的列表,这个列表需要保存在内存中,很明显内存限制了这个问题。
def get_primes(start):
for element in magical_infinite_range(start):
if is_prime(element):
return element
如果使用生成器就不需要返回整个列表,每次都只是返回一个数据,避免了内存的限制问题。
def get_primes(number):
while True:
if is_prime(number):
yield number
number += 1
生成器源码分析
生成器的源码在Objects/genobject.c。
调用栈
在解释生成器之前,需要讲解一下Python虚拟机的调用原理。
Python虚拟机有一个栈帧的调用栈,其中栈帧的是PyFrameObject,位于Include/frameobject.h。
typedef struct _frame {
PyObject_VAR_HEAD
struct _frame f_back; / previous frame, or NULL /
PyCodeObject f_code; / code segment /
PyObject f_builtins; / builtin symbol table (PyDictObject) */
//代码效果参考:https://v.youku.com/v_show/id_XNjQwMDM3NjA2NA==.html
PyObject f_globals; / global symbol table (PyDictObject) /
PyObject f_locals; / local symbol table (any mapping) /
PyObject f_valuestack; / points after the last local /
/ Next free slot in f_valuestack. Frame creation sets to f_valuestack.
Frame evaluation usually NULLs it, but a frame that yields sets it
to the current stack top. /
PyObject f_stacktop;
PyObject f_trace; / Trace function */
/* If an exception is raised in this frame, the next three are used to
* record the exception info (if any) originally in the thread state. See
* comments before set_exc_info() -- it's not obvious.
* Invariant: if _type is NULL, then so are _value and _traceback.
* Desired invariant: all three are NULL, or all three are non-NULL. That
* one isn't currently true, but "should be".
*/
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
PyThreadState *f_tstate;
int f_lasti; /* Last instruction if called */
/* Call PyFrame_GetLineNumber() instead of reading this field
directly. As of 2.3 f_lineno is only valid when tracing is
active (i.e. when f_trace is set). At other times we use
PyCode_Addr2Line to calculate the line from the current
bytecode index. */
int f_lineno; /* Current line number */
int f_iblock; /* index in f_blockstack */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
} PyFrameObject;
栈帧保存了给出代码的的信息和上下文,其中包含最后执行的指令,全局和局部命名空间,异常状态等信息。f_valueblock保存了数据,b_blockstack保存了异常和循环控制方法。
举一个例子来说明,
def foo():
x = 1
def bar(y):
z = y + 2 # <--- (3) ... and the interpreter is here.
return z
return bar(x) # <--- (2) ... which is returning a call to bar ...
foo() # <--- (1) We're in the middle of a call to foo ...