Как работает yield в коде Python C, хорошая и плохая часть

Недавно я изучал код Python. Я знаю, как использовать генераторы (next, send и т. Д.), Но это интересно понять, прочитав код на Python C.

Я нашел код в Object / genobject.c, и это не так сложно (но все же нелегко) понять. Поэтому я хочу знать, как это действительно работает, и убедиться, что у меня нет неправильного понимания генераторов в Python.

Я знаю все звонки

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

и результат возвращается из PyEval_EvalFrameEx который выглядит как динамическая структура фрейма, могу ли я понять это как stack или что-то?

Хорошо, похоже, что Python хранит некоторый контекст в памяти (я прав?). Похоже, что каждый раз, когда мы используем yield, он создает генератор и сохраняет контекст в памяти, хотя и не все функции и переменные.

Я знаю, что если у меня есть большой цикл или большие данные для разбора, то получается потрясающий выход, он экономит много памяти и делает его простым. Но некоторым моим коллегам нравится везде использовать yield, как и return. Нелегко читать и понимать код, и Python хранит контекст для большинства функций, которые никогда не могут быть вызваны снова. Это плохая практика?

Итак, вопросы:

  1. Как PyEval_EvalFrameEx Работа.
  2. Использование памяти по урожайности.
  3. Это плохая практика для использования урожайности везде.

И я нашел, если у меня есть генератор, функция gen_send_ex будет вызываться дважды, почему?

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

test().next()

Будет звонить gen_send_ex дважды, первый раз без аргументов, с аргументами во второй раз, и получите результат.

Спасибо тебе за твое терпение.

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

Мы могли видеть, как он инициирует объект генератора. И Инициировать 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, send - отправка 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;
}

Похоже, что Generator Object является контроллером своего собственного Frame, который называется 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 хранит контекст с помощью фреймового объекта (мы можем искать Call Stack). Вернуть значение и отказаться от контроля над кодом.

После того, как все сделано, то return result и показывая значение 1 в терминале.

В следующий раз, когда мы запустим next(), он не будет f_lasti объем. Это показывает:

frame
here
call here
return result
2

Мы не отправляли аргумент arg, поэтому все еще получаем результат от PyEval_EvalFrameEx, и результат равен 2.

Другие вопросы по тегам