2014-07-01 8 views
5

Recentemente ho esaminato il codice di Python. So come usare i generatori (next, send ed ecc.), Ma è divertente capirlo leggendo il codice Python C.Come funziona la resa in codice Python C, parte buona e cattiva

Ho trovato il codice in Object/genobject.c e non è così difficile (ma ancora non facile) capire. Quindi voglio sapere come funziona davvero e assicurarmi di non avere fraintendimenti sui generatori in Python.

so tutto chiama

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

e il risultato è tornato da PyEval_EvalFrameEx che sembra che sia una struct struttura dinamica, potrei capire come stack o qualcosa del genere?

Ok, sembra che Python memorizzi del contesto in memoria (vero?). Sembra che ogni volta che utilizziamo il rendimento crei un generatore e memorizzi il contesto in memoria, sebbene non tutte le funzioni e le vars.

So che se ho un grande loop o grandi dati da analizzare, la resa è sorprendente, risparmia molta memoria e rende semplice. Ma alcuni dei miei compagni di lavoro amano usare la resa ovunque, proprio come il ritorno. Non è facile leggere e comprendere il codice e Python memorizza il contesto per la maggior parte delle funzioni che potrebbero non essere mai più richiamate. È una cattiva pratica?

Quindi, le domande sono:

  1. Come funziona PyEval_EvalFrameEx lavoro.
  2. Utilizzo memoria della resa.
  3. È una cattiva pratica usare il rendimento ovunque.

E ho trovato se ho un generatore, la funzione gen_send_ex verrà chiamata due volte, perché?

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

test().next() 

Si chiamerà gen_send_ex due volte, la prima volta senza argomenti, con args la seconda volta, e ottenere il risultato.

Grazie per la vostra pazienza.

+1

per 3 #, naturalmente si tratta di una cattiva pratica di utilizzare il protocollo iteratore per una volta gli eventi dove sarebbe sufficiente un semplice 'return'. –

+0

@PauloScardine grazie! – GuoJing

+0

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

risposta

1

ho visto questi articoli:

questo articolo dirmi come funziona PyEval_EvalFrameEx.

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

Questo articolo mi dica la struct telaio in Python.

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

Questi due cose sono molto importanti per noi.

Quindi rispondi alla mia domanda. Non so se ho ragione.

Se ho malinteso o completamente sbagliato, per favore fatemi sapere.

Se Ho codice:

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

Questo è un generatore molto semplice.

f = gen() 

E ogni volta che lo chiamiamo, Python crea un oggetto generatore.

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

Potremmo vederlo avviare un oggetto generatore. E Init a Frame.

Tutto ciò che ci piace o f.send()f.next(), chiamerà gen_send_ex, e il codice qui sotto:

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);             
} 

unica differenza tra due funzioni è arg, invia è inviare un'arg, successivo Inviare NULL.

codice gen_send_ex di seguito:

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

Sembra Generator oggetto è un regolatore di essa la propria cornice in cui si chiedeva gi_frame.

Aggiungo un po 'di fprintf (...), quindi eseguiamo il codice.

f.next() 

f->f_lasti 
here 
call here 
return result 
1 

Quindi, prima si va a f_lasti (Questo è un offset nel bytecode delle ultime istruzioni eseguite intero, inizializzato a -1), e sì è -1, ma senza argomenti, allora la funzione va sopra.

Quindi goto here, la cosa più importante ora è PyEval_EvalFrameEx. PyEval_EvalFrameEx implementa il ciclo di valutazione di CPython, potremmo far funzionare ogni codice (in effetti è Python opcode) ed eseguire la riga print 'call here', stampa testo.

Quando il codice passa a yield, Python memorizza il contesto utilizzando l'oggetto frame (potremmo cercare Call Stack). Dare valore indietro e rinunciare al controllo del codice.

Dopo aver completato il tutto, quindi return result e mostrando il valore 1 nel terminale.

La prossima volta che eseguiremo next(), non andrà allo scope f_lasti. Essa mostra:

frame 
here 
call here 
return result 
2 

Noi non ha mandato arg in modo ancora ottenere il risultato di PyEval_EvalFrameEx e il risultato è pari a 2.

Problemi correlati