国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

深入分析python yield

2019-11-08 20:15:28
字體:
來源:轉載
供稿:網友

一 概述

python中的yield是一個表達式,當函數中出現yield關鍵的時候,該函數會返回一個generator,可以通過迭代generator或者通過generator的send方法來激活generator執行,直到在有yield關鍵字的地方停下來。generator是可迭代的,generator只能迭代一次,因為generator的數據是實時執行計算的。我們通過如下 斐波那契數列實現的例子來直觀的了解下generator的基本使用方法。

def fib_gen(max):	a, b = 0, 1	for i in xrange(max):		# send_value只是用來說明用法的測試		send_value = yield b		if send_value:			PRint "send_value=%s" % send_value		a, b = b, a+b

1 通過迭代方法

# 迭代測試for fib_value in fib_gen(3):	print fib_value# print result112我們可以向迭代器一樣的去迭代generator, 不過generator是順序實時執行的,只能迭代一次。

2 通過send方法觸發迭代器

# send方法的測試ge = fib_gen(2)# or ge.next()print ge.send(None)print ge.next()print ge.send("hello world")# print result11send_value=hello worldTraceback (most recent call last):  File "test.py", line 20, in <module>    print ge.send("hello world")StopIteration

(1) 當我們初次調用函數ge = fib_gen(2)的時候,函數還沒有被執行,只是返回一個generator ge。可以通過send方法來觸發generator的執行。(2) 初次調用必須send(None), 此時函數fib_gen開始執行,執行到yield b時停下來,并返回b。(3) 當我們繼續調用ge.next(相當于ge.send(None)), ge會接著之前停下來的地方繼續執行: send_value = yield b, 此時返回的send_value即為傳遞進去的參數None。繼續執行send函數,從打印的結果可以看出,send_value="hello world"被傳遞到了函數中。(4) 當ge執行到結束的時候,就會拋出StopIteration的異常,與其他的迭代器類似。就這樣,通過send將參數傳遞到函數中,函數通過yield的值作為send的返回值。

二 python虛擬機框架

要理解具體的yield的實現,首先要大概了解一下python虛擬機的執行流程。python中虛擬機類似程序在x86機器上運行時棧的形式,以棧幀為基本單位,形成一個棧幀鏈,執行的時候在這些棧幀鏈中進行切換。在python中,一個模塊、類以及函數的執行都會產生一個棧幀,然后執行這個棧幀。python某個時刻執行的環境的棧幀鏈如下所示。

圖1 Python執行的某個時候的運行環境

棧幀是通過一個PyFrameObject的結構實現,執行某個棧幀的時候,就是一個大的for循環,一條條讀出code的字節碼執行,串行的執行字節碼指令。

三 yield的具體實現

在python中,yield是通過generator來實現,理解generator的具體實現,也就理解了yield的具體原理。

1 generator的結構

在python的源碼中,generator的聲明以及實現在genobject.h以及genobject.c中。先看一下generator的具體實現的結構。

// genobject.htypedef struct {    PyObject_HEAD    /* The gi_ prefix is intended to remind of generator-iterator. */     /* Note: gi_frame can be NULL if the generator is "finished" */    struct _frame *gi_frame;     /* True if generator is being executed. */    int gi_running;         /* The code object backing the generator */    PyObject *gi_code;     /* List of weak reference. */    PyObject *gi_weakreflist;} PyGenObject;

注釋中基本上解釋的比較清楚了,gi_frame就是指向前面介紹的棧幀的指針,generator的主要實現原理就是保存了當前的棧幀(棧幀中同樣記錄著當前執行到哪條字節碼指令)。其他字段是一些輔助的信息,通過注釋可以了解。

2 PyGen_New函數

PyGen_New為geobject提供唯一功能相關的對外接口,PyGen_New的具體實現如下。

// genobject.cPyObject *PyGen_New(PyFrameObject *f){    PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);    if (gen == NULL) {        Py_DECREF(f);        return NULL;    }    gen->gi_frame = f;    Py_INCREF(f->f_code);    gen->gi_code = (PyObject *)(f->f_code);    gen->gi_running = 0;    gen->gi_weakreflist = NULL;    _PyObject_GC_TRACK(gen);    return (PyObject *)gen;}

PyGen_New接受一個PyFramObject 棧幀的指針,設置當前的gi_frame以及gi_code執行,保存當前的環境,返回一個generator,以及PyGenObject。

3 具體實現

(1) 函數調用以及包含yield的函數調用的實現在python的實現中,每個棧幀是通過函數PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)來實現,函數接受一個PyFrameObject棧幀為參數,通過一個for循環,不斷的讀入字節碼執行,通過一個巨大的switch語句,串行的執行字節碼指令。

// ceval.c 代碼有刪減PyObject *PyEval_EvalFrameEx(PyFrameObject *f, int throwflag){    PyThreadState *tstate = PyThreadState_GET();    # 設置當前的frame    tstate->frame = f;    ...    for (;;)    {        switch (opcode)         {            case NOP:            ...            case LOAD_FAST:            ...            case CALL_FUNCTION:                PyObject **sp;                PCALL(PCALL_ALL);                sp = stack_pointer;                x = call_function(&sp, oparg);                stack_pointer = sp;                PUSH(x);                if (x != NULL)                    continue;                break;        }    }}

以菲波那切數列的實現為例,執行.py文件首先會產生一個PyFrameOject, 當執行到ge = fib_gen(2)的時候, 進行了一次函數調用,當前PyEval_EvalFrameEx函數執行到case CALL_FUNTION,進行一個call_funtion的函數調用,最后將返回結果壓棧,繼續執行下一條字節碼指令。我們看下call_function中具體做了什么,在call_funtion的調用中,最終會調用到函數PyEval_EvalCodeEx函數,從函數名字可以看出,這個函數的主要作用就是執行字節碼,函數的部分實現如下。

// ceval.c 代碼有刪減或者修改PyObject *PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,           PyObject **args, int argcount, PyObject **kws, int kwcount,           PyObject **defs, int defcount, PyObject *closure){    register PyFrameObject *f;    register PyObject *retval = NULL;    PyThreadState *tstate = PyThreadState_GET();    f = PyFrame_New(tstate, co, globals, locals);    if (co->co_flags & CO_GENERATOR) {        /* Don't need to keep the reference to f_back, it will be set         * when the generator is resumed. */        Py_CLEAR(f->f_back);         PCALL(PCALL_GENERATOR);         /* Create a new generator that owns the ready to run frame         * and return that as the value. */        return PyGen_New(f);    }     retval = PyEval_EvalFrameEx(f,0);    return retval}

(1) 由函數的實現可以看出,在10行,通過code以及當前的環境變量,生成一個PyFrameObject的棧幀,然后執行該棧幀,將結果進行返回,這樣就完成了一次函數的調用。(2) 但是具有yield的函數返回的generator,具體的實現從第11行的分支開始,將當前frame的f_back清空,從棧幀鏈中移除,等到改frame具體執行的時候,再將其插入到python虛擬機執行的棧幀鏈中。(3) 我們看到,此時并沒有執行該frame,而是直接通過PyGen_New生成一個generator直接返回,這就是我們上面所說的,調用ge = fib_gen(2)其實返回一個generator,函數fib_gen并沒有真正的開始執行。(4) 那函數什么時候開始執行的呢,當我們通過迭代或者顯示調用send的時候,該generator就開始執行起保存的frame,也是通過PyEval_EvalFrameEx函數來執行,如果再次遇到yield語句,如之前的流程一樣,返回一個新的generator。

(2) generator的send函數generator的send函數,激活genrator并執行,知道再次遇到yield返回一個新的generator或者直接執行結束。無論是迭代還是顯示的調用next函數,最終都是通過generator的send函數來實現。generator的send函數的具體實現如下:

// genobject.c 代碼有刪減或者修改static PyObject *gen_send_ex(PyGenObject *gen, PyObject *arg, int exc){    PyThreadState *tstate = PyThreadState_GET();    PyFrameObject *f = gen->gi_frame;    PyObject *result;     if (gen->gi_running) {        PyErr_SetString(PyExc_ValueError,                        "generator already executing");        return NULL;    }    ...    /* Generators always return to their most recent caller, not     * necessarily their creator. */    f->f_tstate = tstate;    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;     /* Don't keep the reference to f_back any longer than necessary.  It     * may keep a chain of frames alive or it could create a reference     * cycle. */    assert(f->f_back == tstate->frame);    Py_CLEAR(f->f_back);    /* Clear the borrowed reference to the thread state */    f->f_tstate = NULL;     return result;}

理解了上述yield函數調用相關原理,generator的send函數就很好理解了。(1) 檢查generator是否正在執行,如果不在執行,或者generator中的frame,并將該frame插入到當前python執行的棧幀鏈中,即f_back指向當前正在執行的frame。(2) 設置當前generator的狀態,并執行當前generator的frame, 清理一些引用,將結果進行返回。(3) 當generator的frame執行完成后,可以接著打斷的frame(即generator frame的f_back指向的frame)繼續執行字節碼指令。(4) 如果在執行generator的frame中再次遇到yield關鍵字,則保存generator的frame(即當前正在執行的frame), 返回結果result為一個新的generator, 當調用該generator的send的時候,重復(1)~(4)

總結:

python的yield通過generator來實現,允許我們可以在函數執行過程中停下來,當調用send的時候繼續執行。我們可以利用python的yield來模擬類似協程方式的實現,利用yield,可以將一些異步的調用通過同步的寫法來實現,后面會寫一個利用yield來實現該方面功能的文章。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 普洱| 井研县| 柘城县| 渭南市| 惠水县| 四平市| 赤峰市| 林芝县| 南漳县| 通化市| 江陵县| 平泉县| 平和县| 简阳市| 桃园市| 新泰市| 黔江区| 嘉黎县| 和政县| 达日县| 珲春市| 南乐县| 大关县| 安乡县| 手游| 衡南县| 河北区| 噶尔县| 华安县| 清新县| 库伦旗| 宿州市| 怀集县| 资中县| 泽库县| 武邑县| 迁安市| 山阴县| 屏东市| 清涧县| 亳州市|