Python yield与实现
yield
的功能类似于return
,但是不同之处在于它返回的是生成器
。
生成器是通过一个或多个yield
表达式构成的函数,每一个生成器都是一个迭代器(但是迭代器不一定是生成器)。
如果一个函数包含yield
关键字,这个函数就会变为一个生成器。
生成器并不会一次返回所有结果,而是每次遇到yield
关键字后返回相应结果,并保留函数当前的运行状态,等待下一次的调用。
由于生成器也是一个迭代器,那么它就应该支持next
方法来获取下一个值。
1234567 | # 通过`yield`来创建生成器def func(): for i in xrange(10); yield i # 通过列表来创建生成器[i for i in xrange(10)] |
12345678910111213141516 | # 调用如下>>> f = func()>>> f # 此时生成器还没有运行<generator object func at 0x7fe01a853820>>>> f.next() # 当i=0时,遇到yield关键字,直接返回0>>> f.next() # 继续上一次执行的位置,进入下一层循环1...>>> f.next()9>>> f.next() # 当执行完最后一次循环后,结束yield语句,生成StopIteration异常Traceback (most recent call last): File \” |
除了next
函数,生成器还支持send
函数。该函数可以向生成器传递参数。
12345678910111213 | >>> def func():... n = 0... while 1:... n = yield n #可以通过send函数向n赋值... >>> f = func()>>> f.next() # 默认情况下n为00>>> f.send(1) #n赋值11>>> f.send(2)2>>> |
最经典的例子,生成无限序列。
常规的解决方法是,生成一个满足要求的很大的列表,这个列表需要保存在内存中,很明显内存限制了这个问题。
1234 | def get_primes(start): for element in magical_infinite_range(start): if is_prime(element): return element |
如果使用生成器就不需要返回整个列表,每次都只是返回一个数据,避免了内存的限制问题。
12345 | def get_primes(number): while True: if is_prime(number): yield number number += 1 |
生成器的源码在Objects/genobject.c
。
在解释生成器之前,需要讲解一下Python虚拟机的调用原理。
Python虚拟机有一个栈帧的调用栈,其中栈帧的是PyFrameObject
,位于Include/frameobject.h
。
1...>>> f.next()9>>> f.next() # 当执行完最后一次循环后,结束yield语句,生成StopIteration异常Traceback (most recent call last): File \”
除了next
函数,生成器还支持send
函数。该函数可以向生成器传递参数。
12345678910111213 | >>> def func():... n = 0... while 1:... n = yield n #可以通过send函数向n赋值... >>> f = func()>>> f.next() # 默认情况下n为00>>> f.send(1) #n赋值11>>> f.send(2)2>>> |
最经典的例子,生成无限序列。
常规的解决方法是,生成一个满足要求的很大的列表,这个列表需要保存在内存中,很明显内存限制了这个问题。
1234 | def get_primes(start): for element in magical_infinite_range(start): if is_prime(element): return element |
如果使用生成器就不需要返回整个列表,每次都只是返回一个数据,避免了内存的限制问题。
12345 | def get_primes(number): while True: if is_prime(number): yield number number += 1 |
生成器的源码在Objects/genobject.c
。
在解释生成器之前,需要讲解一下Python虚拟机的调用原理。
Python虚拟机有一个栈帧的调用栈,其中栈帧的是PyFrameObject
,位于Include/frameobject.h
。
1234567891011121314151617181920212223242526272829303132333435 | 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) */ 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 */ p; /* 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 |
上一篇:Python异常编程技巧