python yield原理_python yield 浅析

标签:

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 的源码后,慢慢发现看源码其实很有意思。看过源码你会发现,原来很多不理解的东西一下子就豁然开朗了:)

标签:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值