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 浅析的主要内容,如果未能解决你的问题,请参考以下文章

python yield 浅析

Python yield 使用浅析

python yield浅析

Python yield 使用浅析

Python yield 使用浅析

Python学习笔记 - yield 使用浅析