标签:
python 的 yield 关键字很多人可能不是很熟悉,最早知道这个关键字是看 xrange 的文档,其中提到了 yield。后来开始用 tornado,对 tornado 的异步模式比较感兴趣,也翻了一下 tornado 的源码,很多东西仍然是一知半解。
最近翻了翻 python 的源码,看了一下 yield 的实现,发现其实原理非常简单!
最简单的用法:
defsimple_yield(start):
n=startwhileTrue:yieldn
n+= 1
if __name__ == ‘__main__‘:for i in simple_yield(5):printiif i >= 10:break
稍微复杂一点的例子:
defsend_yield(start):
n=startwhileTrue:
n= yield
if notn:break
printnif __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, intthrowflag)
{
...caseYIELD_VALUE:
retval=POP();
f->f_stacktop =stack_pointer;
why=WHY_YIELD;gotofast_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;returnretval;
}
上面的源码精简掉了大部分,只保留了几个关键点:
1,解释器执行到 yield 后,取出栈中的最后的元素返回
2,yield 直接跳到了 fast_yield,跳过了正常函数返回的部分,也就是跳过了清理函数现场,这样下次执行的时候才能从返回的地方开始,而不是重现开始执行
再看 send 的代码:
static PyObject *gen_send_ex(PyGenObject*gen, PyObject *arg, intexc)
{
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;
...returnresult;
}
同样省掉了大部分代码,可以看到 send 做的事情:
1,把传入的参数放到栈顶
2,执行 frame
3,返回执行的结果
明白了生成器的实现,再去看 tornado 的异步实现,原来觉得晦涩难懂的地方现在觉得顺畅多了,更能理解 tornado 异步实现的妙处,用起来也是得心应手。
我以前总觉得看源码是枯燥的一件事情,自从前段时间大体看了一下 python 的源码后,慢慢发现看源码其实很有意思。看过源码你会发现,原来很多不理解的东西一下子就豁然开朗了:)
标签: