Шутки с исходным кодом генератора Python

Python

Анализ исходного кода генератора Python

Какой к черту генератор?

Генератор (Generator) стал стандартной функцией python в python2.3, поэтому было добавлено ключевое слово yield (да, это yield, который выдает java-поток).Самая удивительная особенность генератора: функция может возвращать результат несколько раз, вместо того, чтобы возвращать его только один раз, как обычная функция (Бог не волшебник, неудивительно~)

Внутри обычной функции Python добавьте ключевое слово выхода, а Python Parser будет относиться к функции в качестве функции генератора. Но функция генератора не является самим генератором, а завод генератора. Поэтому при вызове генераторной функции, создайте генератор Объект. Когда внешний мир должен получить значение из этого генератора, генератор вернет значение через доход вместо обратного метода обычных функций. В этом процессе вывод тайно делает две вещи:

  • вернуть значение вызывающей стороне
  • Отметьте текущую позицию выполнения, когда генератор снова запустится, возобновите работу с отмеченной позиции

Сказав так много, давайте перейдем к коду

def return_a_generator():  # 这货是个生成器函数
    yield 'foobar'
    yield 42
    yield 'hello'
generator = return_a_generator()   #这步操作只是为了产生生成器对象, 也可以称为激活
next(generator) # 真二八经的第一次调用,next就是一个调用方
'foobar'
next(generator) # 我还可以被调用哦
42
next(generator) # 这么优秀的我还是可以被调用
'hello'
next(generator)  # 好吧玩脱了
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-18-8b45440e27eb> in <module>
----> 1 next(generator)  # 好吧玩脱了


StopIteration: 

О, здесь кратко представлен генератор, давайте приступим к формальному разбору принципа реализации этой волшебной фичи.

Основные объекты среды выполнения Python

В мире python все является объектом, не только мы можем видеть базовые типы (экземпляры, такие как int, str, list и т. д.), но и сам класс также является объектом! Но это ничего, что действительно удивительно, так это то, что все основные компоненты среды выполнения Python (блоки кода, функции, фреймы) также являются объектами. (Чтобы статья не была слишком скучной, мы будем вкраплять кровавую и жестокую любовную драму. Общий сюжет таков, как богиня (кусок кода генератора) с помощью одного запасного колеса, наконец, гуляет с отморозком (процессор) Вместе)

PyCodeObject (запаска №1)

Когда код Python (py-файл) компилируется виртуальной машиной Python (то есть исходный код Python преобразуется в байт-код Python), результат компиляции будет сохранен в файле pyc, а файл pyc Сохраненный формат - это формат сериализации PyCodeObject. Поэтому это первая запаска богини. Истинное содержание PyCodeObject следующее:

typedef struct {
    PyObject_HEAD
    int co_argcount;		/* #arguments, except *args */
    int co_nlocals;		/* #local variables */
    int co_stacksize;		/* #entries needed for evaluation stack */
    int co_flags;		/* CO_..., see below */
    PyObject *co_code;		/* instruction opcodes python字节码,女神本尊*/
    PyObject *co_consts;	/* list (constants used) */
    PyObject *co_names;		/* list of strings (names used) */
    PyObject *co_varnames;	/* tuple of strings (local variable names) */
    PyObject *co_freevars;	/* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest doesn't count for hash/cmp */
    PyObject *co_filename;	/* string (where it was loaded from) 认识女神的地方*/
    PyObject *co_name;		/* string (name, for reference) 女神的名字*/
    int co_firstlineno;		/* first source line number */
    PyObject *co_lnotab;	/* string (encoding addr<->lineno mapping) See
				   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;     /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
} PyCodeObject;
// python2.7 Include/code.h 

Выше приведено определение структуры PyCodeObject c. Поле co_code — это божество записанной богини (код Python, соответствующий коду Python). Поскольку стремление к успеху Прибыла к богине (хотя и ненадолго), место первой встречи (co_filename путь к источнику python), имя богини (имя модуля co_name/название функции), богиня Настройки (другие поля, такие как количество входных параметров функции записи, размер используемого стека) обязательно будут учтены.

PyCodeObject

На приведенном выше рисунке показан результат анализа файла pyc с помощью инструмента. Код представляет собой приведенный выше пример кода. Значение co_flags 0x63 является ключевым моментом в продолжении. Инструмент симулятора, !попросить лайк

ОК! Введение одной запаски закончено. Его основная запись записывает всю статическую информацию функции python, в основном для хранения. Чтобы вернуть операцию, сохраненную из co_code , Память начнет развиваться.

PyfunctionObject (Джек - используется только при изменении шин, вы знаете)

Говорят, что под присмотром запасного колеса Богини № 1 (PyCodeObject) она долгое время находилась на жестком диске, но место, куда она всегда хочет попасть, — это память и процессор, место, которое действительно заставляет ее полный жизненной силы. Наконец, случайно она встретила PyFunctionObject, парня, ответственного за введение людей в память. Итак, богиня рассказала запаске №1 все, что она думает, и, выслушав запаску №1, отдала богиню PyFunctionObject за любовь (но не завидуйте, этот парень — самое жалкое существо в этом сценарии) .

Проще говоря: PyFunctionObject — это функциональный объект python, а функция генератора основана на преобразовании функций, поэтому после загрузки виртуальной машины python из файла pyc первое, чем она становится, — это объект PyFunctionObjec. Его структура определяется следующим образом:

typedef struct {
    PyObject_HEAD
    PyObject *func_code;	/* A code object 1号备胎保存的所有女神信息 */
    PyObject *func_globals;	/* A dictionary (other mappings won't do) */
    PyObject *func_defaults;	/* NULL or a tuple 函数默认值 */
    PyObject *func_closure;	/* NULL or a tuple of cell objects */
    PyObject *func_doc;		/* The __doc__ attribute, can be anything */
    PyObject *func_name;	/* The __name__ attribute, a string object 女神名必须牢记 */
    PyObject *func_dict;	/* The __dict__ attribute, a dict or NULL */
    PyObject *func_weakreflist;	/* List of weak references */
    PyObject *func_module;	/* The __module__ attribute, can be anything */
} PyFunctionObject;
// python2.7 Include/funcobject.h                                              
                                            

В дополнение к полю func_code, которое сохраняет всю информацию о богине (от запаски №1), PyFunctionObject также сохраняет контекст текущей памяти (например, информацию о глобальной переменной func_globals), иначе было бы неловко хвастаться тем, что он рывок перед богиней памяти.

А вот PyFunctionObject этим ограничивается, он может только круглый год возиться в памяти, а никакого контакта с CPU (конечная цель богини) у него нет. Так что его отношениям с богиней суждено быть недолгими (только домкрат для смены шин), и вскоре появился PyFrameObject.

PyFrameObject (запаска 2)

Говорят, что PyFrameObject (frame object) — это группа ребят, которые в ранние годы учились за границей и изучили принцип вызова функций на языке C. После возвращения на python они сразу работали под CPU, так что давайте посмотрим на вызов функции на языке C. Тип воспроизведения, см. рисунок ниже:

CCall

Каждый стек фрейма сохраняет информацию о вызове функции (параметры функции, локальные переменные и т. д.), и цепочка вызовов функций поддерживается таким фрагментом данных стека.PyFrameObject имитирует эту структуру, а затем вызывает ветер и дождь в питоне. составляет:

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;	/* 上一个frame,可能为None c那边学过来的精髓,构造调用链 */
    PyCodeObject *f_code;	/* PyCodeObject对象 我们的女神*/
    PyObject *f_builtins;	/* builtin 命名空间  (PyDictObject) */
    PyObject *f_globals;	/* global 命名空间 (PyDictObject) */
    PyObject *f_locals;		/* local 命名空间 (any mapping) */
    PyObject **f_valuestack;	/* 运行时栈底 */
    PyObject **f_stacktop;   /* 运行时栈顶 */
    PyObject *f_trace;		/* Trace function */
    PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
    PyThreadState *f_tstate; /* 当前的线程环境 */
    int f_lasti;		/*  上一条字节码指令在f_code中的偏移位置 */
   
    int f_lineno;		/* Current line number */
    int f_iblock;		/* index in f_blockstack */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];	/* 局部变量(入参也是局部变量) + 内层约束变量 + 自由变量 + 栈  */ 
} PyFrameObject;
// python2.7 Include/frameobject.h  

Как было сказано выше, богиня случайно обнаружила, что PyFrameObject является смыслом ее памяти, поэтому тут же отправила хорошую карту PyFunctionObject, но богиня PyFunctionObject еще не нагрела руки, но и по любви познакомила богиню с PyFrameObject . PyFrameObject очень рад узнать о богине от PyFunctionObject и официально датирован.Конечно, вся информация о богине также получена от PyFunctionObject (в поле f_code)

обзор роли

  • PyCodeObject сохраняет статическую информацию кода Python
  • Представление памяти во время выполнения функционального объекта PyFunctionObject
  • Реальный объект выполнения виртуальной машины PyFrameObject Python

Процесс вызова генератора Python (повседневная жизнь процессора-подонка)

С помощью запаски и домкрата богиня очень близко подобралась к процессору, отморозку.Теперь процессор собирается показать свои настоящие технологии.

Отморозки в рассказах вообще более крайние, и наш не исключение. У этого отморозка главное каждый день - влюбиться в богиню, и в большинстве случаев: в общении с богиней он находит ее лучшую подругу, так что нынешнюю богиню он отложит в сторону, и перейдет к флирту с ее лучшей подругой, а потом поговорить с её лучшей подругой.Во время контакта лучшая подруга лучшей подруги которую я знаю...(эта операция может быть рекурсивно рекурсивно),тогда группу богинь нарисованную такой богиней можно назвать "кластер богинь ". Конечно, как отморозок, он обязательно пройдет через больше Каждую богиню. Создание нескольких кластеров богини (т.е. многопоточный механизм)

PS:一直有人吐槽python的GIL锁导致多核利用不起来,没错这是真的。但是python在遇到IO访问时(网络访问,磁盘读取),当前线程会主动释放GIL锁,所以面对IO密集型操作,python多线程还不是太过鸡肋。(当然协程出现后,线程地位就跟尴尬了)

Чтобы быстро найти удовлетворительную богиню, этот подонок (CPU) искал богиню, которая уживалась с PyFrameObject. Поскольку объектов PyFrameObject слишком много, и для переключения между несколькими кластерами богини специально сформулирована памятка PyThreadState, формат которой выглядит следующим образом.

typedef struct _ts {
    /* See Python/ceval.c for comments explaining most fields */

    struct _ts *next;
    PyInterpreterState *interp; // 进程信息

    struct _frame *frame; // PyFrameObject对象列表,构成调用链
    int recursion_depth;
    /* 'tracing' keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

	  PyObject *curexc_type;		// 女神交往时的异常信息,确保一个女神谈崩了,不会影响其他人
    PyObject *curexc_value;     // 
    PyObject *curexc_traceback; //

    PyObject *exc_type;     // 当前女神的交往信息,免得多个女神簇回切换后忘了之前聊到哪了
    PyObject *exc_value;
    PyObject *exc_traceback;

    PyObject *dict;  /* Stores per-thread state */

    int tick_counter; 

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */
    long thread_id; /* Thread id where this tstate was created */

} PyThreadState;

PyThreadState — это структура данных для python для записи информации о потоке, но это не объект python.В основном он записывает цепочку вызовов стека фреймов в текущем потоке, выполнение текущего фрейма и, грубо говоря, контекст потока ( при переключении потоков в системе linux в основном нужно сохранять регистры каждого класса, эти регистры также содержат аналогичную информацию) Он также имеет ссылку на PyInterpreterState, который записывает текущую информацию о процессе, и не будет здесь раскрываться.

После введения PyThreadState ежедневные операции ЦП выглядят следующим образом:

all

Каждое PyThreadState записывает любовный прогресс кластера богинь и переключается между различными кластерами богинь в зависимости от настроения. хорошо, теперь вы можете посмотреть операцию флирта:

PyEval_EvalFrameEx -- Механизм выполнения виртуальной машины Python (место маленькой девочки)

Следующий код был значительно упрощен и доработан.

PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
    PyThreadState *tstate = PyThreadState_GET(); // 拿出备忘录
    tstate->frame = f;  // 将当前PyFrameObject记录到备忘录里
    PyCodeObject *co = f->f_code; // 从PyFrameObject轻松搭上了女神

    // cpu撩妹众多,已经通过强化学习方法,深刻掌握女神在不同表现下,
    // 应该有的的应对方案(比如肚子疼,立马上热水/感冒了,立马上热水等等)
    // 并起名为“状态机”

    first_instr = PyString_AS_STRING(co->co_code);  //女神第一个举动
    next_instr = first_instr + f->f_lasti + 1; //女神第下一个举动

    // 开始交往了
    for (;;) {

    fast_next_opcode:
        opcode = NEXTOP(); // 获取到女神的当前举动
        switch (opcode) { // 根据不同举动,采用不同应对方案
          case NOP:    // 女神啥举动也没有
            goto fast_next_opcode;  // 敌不动我不动,等待下一个举动
                         
          case MAKE_FUNCTION:    // 女神介绍闺蜜
          {
              v = POP(); 
              x = PyFunction_New(v, f->f_globals); // 安排一个PyFunction把闺蜜接到内存,所以所谓的偶然都是安排好的
              PUSH(x);
              break;
          }

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

          default:   // 女神这个举动之前没见过啊
            fprintf(stderr,
                "XXX lineno: %d, opcode: %d\n",
                PyFrame_GetLineNumber(f),
                opcode);
            PyErr_SetString(PyExc_SystemError, "unknown opcode");
            why = WHY_EXCEPTION;
            break;
        } /* switch */ 
    } /* main loop */

exit_eval_frame:
    Py_LeaveRecursiveCall();
    tstate->frame = f->f_back;

    return retval;
}

Через приведенный выше код вы должны понять основную операцию соединения процессора с богиней (было бы неплохо иметь такой конечный автомат, но, к сожалению, в реальности девушки должны быть хаотичными)

хорошо, давайте продолжим узнавать больше о том, как процессор связался с лучшим другом богини.

Чтобы текущая богиня не узнала, что у него есть контакт с ее девушкой, процессор дважды инкапсулирован (call_function->fast_function->PyEval_EvalCodeEx python одна из ссылок вызова)

static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);  // 从PyFunction获取女神闺蜜的信息
    
    // 此处剧情需要,略过n行代码

    return PyEval_EvalCodeEx(co, globals,
                             (PyObject *)NULL, (*pp_stack)-n, na,
                             (*pp_stack)-2*nk, nk, d, nd,
                             PyFunction_GET_CLOSURE(func));
    
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
      PyObject **args, int argcount,   // 位置参数
      PyObject **kws, int kwcount,   // 关键字参数
      PyObject **defs, int defcount,   // 默认参数
      PyObject *closure)        // 闭包
{
  PyThreadState *tstate = PyThreadState_GET();

    register PyFrameObject *f;  
    f = PyFrame_New(tstate, co, globals, locals); // 新找的一位PyFrameObject, 将这位闺蜜先安排给他,所谓的偶遇都是cpu幕后操作的结果

    // #define CO_GENERATOR    0x0020 (注意定义哦,我特意从其他地方捞过来的)
    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_XDECREF(f->f_back);
        f->f_back = NULL;

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

Путем двухшаговой кокетливой операции fast_function и PyEval_EvalCodeEx ЦП официально дошел до подруг богини через PyFunctionObject и PyFrameObject Выше приведен процесс работы ЦП для обычных богинь. Но для нашей богини-генератора (богиня, у которой установлено co_flags 0x20, флаги функций, которые мы сгенерировали ранее, равны 0x63, поэтому установлено 0x20, поэтому этот маленький флажок является ключом к различению обычных функций и функций-генераторов) особенно благоприятен, поэтому это договоренность Хозяйка богини PyGenObject, всегда обращайте внимание на положение богини-генератора в PyFrameObject, ее структура выглядит следующим образом:

typedef 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; // 当前女神所处的PyFrameObject

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

Из-за особой обработки богини-генератора ЦП не решается сохранить информацию о богине-генераторе в памятке, которая обрабатывается PyGenObject. Но как только с текущей богиней что-то не так, процессор может немедленно найти соответствующий PyFrameObject через PyGenObject, и соответствующая операция выглядит следующим образом:

static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
    PyThreadState *tstate = PyThreadState_GET(); // 拿到备忘录
    PyFrameObject *f = gen->gi_frame; // 先找到当前女神所处的PyFrameObjec
    PyObject *result;

    /* Generators always return to their most recent caller, not
     * necessarily their creator. */
    f->f_back = tstate->frame; // 将当前PyFrameObject记录到备忘录,不然就在不同女神族之间切换,容易忘了

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

    return result;
}

Если богине-генератору надоело болтать с процессором (выходом), то процессор будет

tstate->frame = f->f_back; 

Сотрите подсказки богини-генератора из памятки, PyGenObject все равно наблюдает, но если ее обнаружат другие богини, это будет большой проблемой.

конец

Говоря о людях, принцип реализации генератора Python заключается в том, что он основан на «свободном» объекте кадра (PyFrameObject), когда вызывается генератор, вызывается «свободный» объект кадра. Смонтируйте его в текущий стек фреймов для выполнения.Когда доходность генератора возвращается, он возвращает текущее значение и выгружает его из стека фреймов. Предоставляет ряд дружественных интерфейсов на уровне пользователя через объект-генератор. Рот инкапсулирует тот факт, что PyFrameObject сохраняется внутри. Вот и все, и это краеугольный камень однопоточной реализации сопрограмм Python.