2014-07-01 47 views
5

最近我一直在研究Python的代碼。我知道如何使用生成器(next,send等),但通過閱讀Python C代碼來理解它很有趣。在Python C代碼中,良率和不良部分的收益如何工作

我在Object/genobject.c找到了代碼,它並不那麼難(但仍不容易)理解。所以我想知道它是如何工作的,並確保我對Python中的生成器沒有誤解。

我知道的一切要求

static PyObject * 
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc) 

和結果從PyEval_EvalFrameEx它看起來就像是一個動態幀結構回來,我可以把它理解爲stack什麼?

好吧,它看起來像Python存儲在內存中的一些上下文(我是嗎?)。它看起來像我們每次使用yield都會創建一個生成器,並將該上下文存儲在內存中,但不是所有的函數和變量。

我知道如果我有大循環或大數據來分析,產量是驚人的,它可以節省大量的內存並使其變得簡單。但是我的一些同事喜歡在任何地方使用收益率,就像回報一樣。閱讀和理解代碼並不容易,Python存儲大部分可能再也不會被調用的函數的上下文。這是不好的做法嗎?

所以,問題是:

  1. 如何PyEval_EvalFrameEx工作。
  2. 內存使用率。
  3. 在各地使用收益是不好的做法。

而我發現如果我有一個發生器,函數gen_send_ex會被調用兩次,爲什麼?

def test(): 
    while 1: 
     yield 'test here' 

test().next() 

它將調用gen_send_ex兩次,第一次用無參數,與ARGS第二次,並得到結果。

感謝您的耐心等待。

+1

爲#3,當然,這是一個不好的做法,使用一次性事件迭代器協議一個簡單的'return'就足夠了。 –

+0

@PauloScardine謝謝! – GuoJing

+0

http://cn.slideshare.net/dabeaz/generators-the-final-frontier –

回答

1

我看到這些文章:

這篇文章告訴我該怎麼做PyEval_EvalFrameEx工作。

http://tech.blog.aknin.name/2010/09/02/pythons-innards-hello-ceval-c-2/

這篇文章告訴我,在Python框架結構。

http://tech.blog.aknin.name/2010/07/22/pythons-innards-interpreter-stacks/

這兩個東西是對我們非常重要。

那麼讓我自己回答我的問題。我不知道我是否正確。

如果我有誤解或完全錯誤,請讓我知道

如果我有代碼:

def gen():                                         
    count = 0                   
    while count < 10:                 
     count += 1                  
     print 'call here'                
     yield count 

這是一個非常簡單的發電機。

f = gen() 

而且每次我們調用它時,Python都會創建一個生成器對象。

PyObject *                   
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;               
} 

我們可以看到它init是一個生成器對象。並初始化一個Frame

什麼我們不喜歡f.send()f.next(),它會調用gen_send_ex,和下面的代碼:功能

static PyObject *                  
gen_iternext(PyGenObject *gen)                                   
{                      
    return gen_send_ex(gen, NULL, 0);             
} 

static PyObject *                  
gen_send(PyGenObject *gen, PyObject *arg)            
{                      
    return gen_send_ex(gen, arg, 0);             
} 

唯一的區別之間二是阿根廷,發送被髮送ARG,未來派NULL。

gen_send_ex下面的代碼:

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) { 
     fprintf(stderr, "gi init\n"); 
     PyErr_SetString(PyExc_ValueError, 
         "generator already executing"); 
     return NULL; 
    } 
    if (f==NULL || f->f_stacktop == NULL) { 
     fprintf(stderr, "check stack\n"); 
     /* Only set exception if called from send() */ 
     if (arg && !exc) 
      PyErr_SetNone(PyExc_StopIteration); 
     return NULL; 
    } 

    if (f->f_lasti == -1) { 
     fprintf(stderr, "f->f_lasti\n"); 
     if (arg && arg != Py_None) { 
      fprintf(stderr, "something here\n"); 
      PyErr_SetString(PyExc_TypeError, 
          "can't send non-None value to a " 
          "just-started generator"); 
      return NULL; 
     } 
    } else { 
     /* Push arg onto the frame's value stack */ 
     fprintf(stderr, "frame\n"); 
     if(arg) { 
      /* fprintf arg */ 
     } 
     result = arg ? arg : Py_None; 
     Py_INCREF(result); 
     *(f->f_stacktop++) = result; 
    } 

    fprintf(stderr, "here\n"); 
    /* 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; 

    /* 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); 

    /* If the generator just returned (as opposed to yielding), signal 
    * that the generator is exhausted. */ 
    if (result == Py_None && f->f_stacktop == NULL) { 
     fprintf(stderr, "here2\n"); 
     Py_DECREF(result); 
     result = NULL; 
     /* Set exception if not called by gen_iternext() */ 
     if (arg) 
      PyErr_SetNone(PyExc_StopIteration); 
    } 

    if (!result || f->f_stacktop == NULL) { 
     fprintf(stderr, "here3\n"); 
     /* generator can't be rerun, so release the frame */ 
     Py_DECREF(f); 
     gen->gi_frame = NULL; 
    } 
    fprintf(stderr, "return result\n"); 
    return result; 
} 

貌似發電機對象是它自己的框架,其被稱爲gi_frame的控制器。

我添加一些fprintf中(...),所以讓我們運行代碼。

f.next() 

f->f_lasti 
here 
call here 
return result 
1 

所以,首先它去f_lasti(這是一個整數偏移進入最後執行的指令字節碼,初始化爲-1),是的,它是-1,但無參數,然後去功能上。

,則跳轉here,最重要的是現在PyEval_EvalFrameEx。 PyEval_EvalFrameEx實現CPython的的評價循環,我們可以事它運行的每個代碼(其實是Python的操作碼),並運行線print 'call here',其打印文本。

當代碼去yield,Python的使用框架對象存儲上下文(我們可以搜索調用堆棧)。回饋價值並放棄對代碼的控制。

一切完成之後,然後return result,並示出了終端值1

下一次,我們接下來的運行(),它不會去f_lasti範圍。它顯示:

frame 
here 
call here 
return result 
2 

我們沒有送ARG所以還是從PyEval_EvalFrameEx得到結果和結果是2