python yield 浅析
Posted franksmith
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python yield 浅析相关的知识,希望对你有一定的参考价值。
python 的 yield 关键字很多人可能不是很熟悉,最早知道这个关键字是看 xrange 的文档,其中提到了 yield。后来开始用 tornado,对 tornado 的异步模式比较感兴趣,也翻了一下 tornado 的源码,很多东西仍然是一知半解。
最近翻了翻 python 的源码,看了一下 yield 的实现,发现其实原理非常简单!
最简单的用法:
def simple_yield(start): n = start while True: yield n n += 1 if __name__ == ‘__main__‘: for i in simple_yield(5): print i if i >= 10: break
稍微复杂一点的例子:
def send_yield(start): n = start while True: n = yield if not n: break print n if __name__ == ‘__main__‘: gen = send_yield(10) gen.send(None) gen.send(15) gen.send(20) gen.send(0) #这行代码会使 send_yield 退出 gen.send(14) #这行代码会出错,因为 gen 已经退出了
这个例子刚好和上一个例子反过来,第一个例子是从 simple_yield 中拿数据,这个例子是把数据发送到 send_yield 中。
简单的说,yield 会立即返回(可能有返回值),函数本身并没有结束,只是被挂起,直到下次调用(for 循环会调用 next 方法)再从挂起的地方(yield)开始执行。
python 解释器执行到 yield 时会返回后面的变量(第二个例子返回了None),当下次回来执行的时候(for 循环会自动调用 next 方法,next 会调用 send)yield 会把 send 的参数返回(第一个例子中的返回值被抛弃了)
具体实现:
当一个函数中出现了 yield 关键字,这个函数就不再是普通的函数了,而是会变成一个叫生成器(generator)的东西,我们来看一下具体的源码:
PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) { ... case YIELD_VALUE: retval = POP(); f->f_stacktop = stack_pointer; why = WHY_YIELD; goto fast_yield; ... assert(why != WHY_YIELD); /* Pop remaining stack entries. */ while (!EMPTY()) { v = POP(); Py_XDECREF(v); } if (why != WHY_RETURN) retval = NULL; fast_yield: ... /* pop frame */ exit_eval_frame: Py_LeaveRecursiveCall(); tstate->frame = f->f_back; return retval; }
上面的源码精简掉了大部分,只保留了几个关键点:
1,解释器执行到 yield 后,取出栈中的最后的元素返回
2,yield 直接跳到了 fast_yield,跳过了正常函数返回的部分,也就是跳过了清理函数现场,这样下次执行的时候才能从返回的地方开始,而不是重现开始执行
再看 send 的代码:
static PyObject * gen_send_ex(PyGenObject *gen, PyObject *arg, int exc) { PyThreadState *tstate = PyThreadState_GET(); PyFrameObject *f = gen->gi_frame; PyObject *result; ... /* Push arg onto the frame‘s value stack */ result = arg ? arg : Py_None; Py_INCREF(result); *(f->f_stacktop++) = result; } /* Generators always return to their most recent caller, not * necessarily their creator. */ Py_XINCREF(tstate->frame); assert(f->f_back == NULL); f->f_back = tstate->frame; gen->gi_running = 1; result = PyEval_EvalFrameEx(f, exc); gen->gi_running = 0; ... return result; }
同样省掉了大部分代码,可以看到 send 做的事情:
1,把传入的参数放到栈顶
2,执行 frame
3,返回执行的结果
明白了生成器的实现,再去看 tornado 的异步实现,原来觉得晦涩难懂的地方现在觉得顺畅多了,更能理解 tornado 异步实现的妙处,用起来也是得心应手。
我以前总觉得看源码是枯燥的一件事情,自从前段时间大体看了一下 python 的源码后,慢慢发现看源码其实很有意思。看过源码你会发现,原来很多不理解的东西一下子就豁然开朗了:)
以上是关于python yield 浅析的主要内容,如果未能解决你的问题,请参考以下文章