Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CPython internals. Part I

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →

CPython internals. Part I

Краткое описание о том, как интерпретатор CPython делает то, что он должен делать

Avatar for svartalf

svartalf

April 02, 2015

Other Decks in Programming

Transcript

  1. Простой пример $ python3.4 -c ‘a = 1; b =

    a + 2; print(b)’ 3 Что происходит, когда мы хотим посчитать сумму двух чисел с помощью python? Возьмем CPython 3.4.0, его исходники и попробуем посмотреть. 2
  2. «C» is for C int main(int argc, char **argv){ …

    return Py_Main(argc, argv); … } 3 ./Modules/python.c
  3. int Py_Main(int argc, char **argv) case 'S': Py_NoSiteFlag++; break; case

    'E': /* Already handled above */ break; case 't': Py_TabcheckFlag++; break; case 'u': unbuffered++; saw_unbuffered_flag = 1; break; case 'v': Py_VerboseFlag++; break; 4 ./Modules/main.c case 'x': skipfirstline = 1; break; /* case 'X': reserved for implementation-specific arguments */ case 'U': Py_UnicodeFlag++; break; case 'h': case '?': help++; break; case 'V': version++; break;
  4. void Py_Initialize(void) Инициализация интерпретатора: PyInterpreterState *interp; PyThreadState *tstate; Инициализация builtins

    и модуля sys: bimod = _PyBuiltin_Init(); interp->builtins = PyModule_GetDict(bimod); sysmod = _PySys_Init(); interp->sysdict = PyModule_GetDict(sysmod); PySys_SetPath(Py_GetPath()); PyDict_SetItemString(interp->sysdict, "modules", interp->modules); 5 ./Python/pythonrun.c
  5. Что хочет пользователь? Выбираем из аргументов программы: • код из

    строки (-c); • код из модуля (-m); • выполнить файл (python3.4 foo.py); • запустить REPL. Для выполнения кода, переданного с помощью «-c» используется PyRun_SimpleStringFlags() 6
  6. int PyRun_SimpleStringFlags(…) Инициализация пространства имен __main__: PyObject *m = PyImport_AddModule("__main__");

    Зачем? Помните «a = 1»? Где хранить переменную «a»? В этом пространстве имен. Затем выполняется переданная строка с кодом: v = PyRun_StringFlags(command, Py_file_input, d, d, flags); 7 ./Modules/pythonrun.c
  7. Парсинг введенной строки 1. Разделение на токены; 2. Создание Concrete

    Syntax Tree; 3. Конвертация CST в Abstract Syntax Tree; 4. Конвертация AST в байт-код для VM. 8 PEP-0339
  8. Concrete Syntax Tree >>> parser.suite('a = 1; b = a

    + 2; print(b)').totuple() (257, (268, (269, (270, (271, (272, (302, (306, (307, (308, (309, (312, (313, (314, (315, (316, (317, (318, (319, (320, (1, 'a')))))))))))))))), (22, '='), (272, (302, (306, (307, (308, (309, (312, (313, (314, (315, (316, (317, (318, (319, (320, (2, '1')))))))))))))))))), (13, ';'), (270, (271, (272, (302, (306, (307, (308, (309, (312, (313, (314, (315, (316, (317, (318, (319, (320, (1, 'b')))))))))))))))), (22, '='), (272, (302, (306, (307, (308, (309, (312, (313, (314, (315, (316, (317, (318, (319, (320, (1, 'a'))))), (14, '+'), (317, (318, (319, (320, (2, '2')))))))))))))))))), (13, ';'), (270, (271, (272, (302, (306, (307, (308, (309, (312, (313, (314, (315, (316, (317, (318, (319, (320, (1, 'print')), (322, (7, '('), (330, (331, (302, (306, (307, (308, (309, (312, (313, (314, (315, (316, (317, (318, (319, (320, (1, 'b'))))))))))))))))), (8, ')')))))))))))))))))), (4, ''))), (4, ''), (0, '')) 9
  9. Abstract Syntax Tree >>> tree = ast.parse('a = 1; b

    = a + 2; print(b)') >>> ast.dump(tree) Module(body=[ Assign(targets=[Name(id='a', ctx=Store())], value=Num(n=1)), Assign(targets=[Name(id='b', ctx=Store())], value=BinOp( left=Name(id='a', ctx=Load()), op=Add(), right=Num(n=2))), Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Name(id='b', ctx=Load())], keywords=[], starargs=None, kwargs=None)) ]) 10
  10. Байт-код программы >>> s = 'a = 1; b =

    a + 2; print(b)' >>> code = compile(s, '<string>', 'exec') >>> code <code object <module> at 0x7ff069ee7a50, file "<string>", line 1> >>> code.co_code b'd\x00\x00Z\x00\x00e\x00\x00d\x01\x00\x17Z\x01\x00e\x02\x00e\x0 1\x00\x83\x01\x00\x01d\x02\x00S' 11
  11. Что это вообще такое? >>> dis.dis(code) 1 0 LOAD_CONST 0

    (1) 3 STORE_NAME 0 (a) 6 LOAD_NAME 0 (a) 9 LOAD_CONST 1 (2) 12 BINARY_ADD 13 STORE_NAME 1 (b) 16 LOAD_NAME 2 (print) 19 LOAD_NAME 1 (b) 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 25 POP_TOP 26 LOAD_CONST 2 (None) 29 RETURN_VALUE 12
  12. Работаем со стеком 0 LOAD_CONST 0 (1) 3 STORE_NAME 0

    (a) 13 ./Python/ceval.c Загружаем в стек значение «1» Присваиваем переменной «a» значение. Значение берется из стека. В результате: a = 1
  13. 6 LOAD_NAME 0 (a) 9 LOAD_CONST 1 (2) 12 BINARY_ADD

    13 STORE_NAME 1 (b) Помещаем в стек значение переменной «a». Помещаем в стек число «2». Стек: [1, 2] Вызываем макрос для сложения BINARY_ADD. BINARY_ADD берет из стека верхние два числа и складывает их. Результат выполнения кладется в стек. Стек: [3] Берем верхнее значение стека и присваиваем его в переменную «b» 14
  14. 16 LOAD_NAME 2 (print) 19 LOAD_NAME 1 (b) 22 CALL_FUNCTION

    1 (1 positional, 0 keyword pair) 25 POP_TOP 26 LOAD_CONST 2 (None) 29 RETURN_VALUE Загружаем в стек ссылку на объект «print» (функцию, но в Python же нет разницы, всё — объекты) Загружаем в стек значение переменной «a» Стек: [print, 3] Вызываем макрос CALL_FUNCTION; он берет верхнее значение стека и вызывает у него __call__, передавая туда второе значение стека. Подчищаем за собой стек (POP_TOP), загружаем в стек объект None и возвращаем его (функция print возвращает None) 15
  15. BINARY_ADD w = POP(); v = TOP(); if (PyInt_CheckExact(v) &&

    PyInt_CheckExact(w)) { /* INLINE: int + int */ register long a, b, i; a = PyInt_AS_LONG(v); b = PyInt_AS_LONG(w); /* cast to avoid undefined behaviour on overflow */ i = (long)((unsigned long)a + b); if ((i^a) < 0 && (i^b) < 0) goto slow_add; x = PyInt_FromLong(i); } ... 16
  16. 17 У нас есть пространство имен «__main__» и объект «code».

    run_mod, v = PyEval_EvalCode(co, globals, locals); В нашем случае пространства имен globals и locals одинаковы, это __main__. PyEval_EvalCode генерирует фрейм (Call stack frame) из кода и пространств имен. Помните про инициализацию интерпретатора и главного потока в Py_Initialize()? Фрейм помещается в стек вызовов (Call stack) потока и выполняется там. И что с этим теперь делать? ./Python/pythonrun.c