3. Вспомнить все
Наверняка не все присутствовали на первой части моего
выступления
“Внутренности” CPython, часть вторая 3
4. Вспомнить все
Наверняка не все присутствовали на первой части моего
выступления
Это ведь было аж в июне
“Внутренности” CPython, часть вторая 3
5. Вспомнить все
Наверняка не все присутствовали на первой части моего
выступления
Это ведь было аж в июне
Поэтому кратко повторю, о чем мы собираемся разговаривать
“Внутренности” CPython, часть вторая 3
8. CPython
Основная реализация на сегодняшний день
Написана на C
“Чистый” интерпретатор байткода, без JIT и AOT
“Внутренности” CPython, часть вторая 4
9. CPython
Основная реализация на сегодняшний день
Написана на C
“Чистый” интерпретатор байткода, без JIT и AOT
“Своя” VM, никаких отсылок к Java и .NET
“Внутренности” CPython, часть вторая 4
12. Альтернативы
native+ - PyPy
.NET - IronPython, Boo (правда, строго говоря это не Python)
JVM - Jython
“Внутренности” CPython, часть вторая 5
13. Альтернативы
native+ - PyPy
.NET - IronPython, Boo (правда, строго говоря это не Python)
JVM - Jython
Возможно другие. Они нас сегодня не интересуют.
“Внутренности” CPython, часть вторая 5
14. Альтернативы
native+ - PyPy
.NET - IronPython, Boo (правда, строго говоря это не Python)
JVM - Jython
Возможно другие. Они нас сегодня не интересуют.
Мы рассматриваем внутреннее устройство CPython.
“Внутренности” CPython, часть вторая 5
15. Все - объект
В CPython все является объектом
“Внутренности” CPython, часть вторая 6
16. Все - объект
В CPython все является объектом
Конкретнее - PyObject*
“Внутренности” CPython, часть вторая 6
17. Все - объект
В CPython все является объектом
Конкретнее - PyObject*
Числа, строки, код, фреймы, модули - абсолютно все
“Внутренности” CPython, часть вторая 6
18. Все - объект
В CPython все является объектом
Конкретнее - PyObject*
Числа, строки, код, фреймы, модули - абсолютно все
Любой PyObject начинается одинаково:
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
“Внутренности” CPython, часть вторая 6
19. PyObject
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
ob_refcnt - reference count для управления памятью
“Внутренности” CPython, часть вторая 7
20. PyObject
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
ob_refcnt - reference count для управления памятью
ob_type - type object, задающий поведение конкретного объекта
“Внутренности” CPython, часть вторая 7
21. PyObject
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
ob_refcnt - reference count для управления памятью
ob_type - type object, задающий поведение конкретного объекта
То, что дальше, может быть чем угодно.
“Внутренности” CPython, часть вторая 7
22. code object
Код в Python - тоже объект.
“Внутренности” CPython, часть вторая 8
23. code object
Код в Python - тоже объект.
Интерпретатор оперирует байткодом стековой машины.
“Внутренности” CPython, часть вторая 8
24. code object
Код в Python - тоже объект.
Интерпретатор оперирует байткодом стековой машины.
Инструкции занимают 1 байт, опционально принимают один
двухбайтный аргумент.
“Внутренности” CPython, часть вторая 8
25. code object
Код в Python - тоже объект.
Интерпретатор оперирует байткодом стековой машины.
Инструкции занимают 1 байт, опционально принимают один
двухбайтный аргумент.
Константы сложных типов хранятся “рядом” в code object,
обращение к ним происходит по индексу.
“Внутренности” CPython, часть вторая 8
28. Стековая машина
На уровне байткода CPython является стековой машиной.
“Внутренности” CPython, часть вторая 11
29. Стековая машина
На уровне байткода CPython является стековой машиной.
Значения кладутся на стек, операции берут их с вершины стека,
иногда меняя порядок верхних элементов.
“Внутренности” CPython, часть вторая 11
30. Стековая машина
На уровне байткода CPython является стековой машиной.
Значения кладутся на стек, операции берут их с вершины стека,
иногда меняя порядок верхних элементов.
Стек значений не имеет отношения к стеку адресов возврата - это
отдельная сущность.
“Внутренности” CPython, часть вторая 11
31. Стековая машина, пример
def f(a,b,c): return a + b*c
0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 LOAD_FAST 2 (c)
9 BINARY_MULTIPLY
10 BINARY_ADD
11 RETURN_VALUE
“Внутренности” CPython, часть вторая 12
32. Нелокальный выход
Стек значений (value stack) существует в рамках одного
исполнения code object
“Внутренности” CPython, часть вторая 13
33. Нелокальный выход
Стек значений (value stack) существует в рамках одного
исполнения code object
Python поддерживает нелокальный выход (break, continue,
raise)
“Внутренности” CPython, часть вторая 13
34. Нелокальный выход
Стек значений (value stack) существует в рамках одного
исполнения code object
Python поддерживает нелокальный выход (break, continue,
raise)
Необходимо “открутить” стек значений на момент входа в for,
while или локальный try
“Внутренности” CPython, часть вторая 13
35. Нелокальный выход
Стек значений (value stack) существует в рамках одного
исполнения code object
Python поддерживает нелокальный выход (break, continue,
raise)
Необходимо “открутить” стек значений на момент входа в for,
while или локальный try
С++ использует для stack unwind при исключениях пометки в
“общем” стеке
“Внутренности” CPython, часть вторая 13
36. Нелокальный выход
Стек значений (value stack) существует в рамках одного
исполнения code object
Python поддерживает нелокальный выход (break, continue,
raise)
Необходимо “открутить” стек значений на момент входа в for,
while или локальный try
С++ использует для stack unwind при исключениях пометки в
“общем” стеке
CPython использует для этих целей block stack.
“Внутренности” CPython, часть вторая 13
37. Block stack
Block stack хранит в себе пары вида (адрес, глубина стека)
“Внутренности” CPython, часть вторая 14
38. Block stack
Block stack хранит в себе пары вида (адрес, глубина стека)
Инструкции вроде break “откручивают” value stack до
запомненного значения глубины и делают goto на указанный
адрес в байткоде
“Внутренности” CPython, часть вторая 14
39. Block stack
Block stack хранит в себе пары вида (адрес, глубина стека)
Инструкции вроде break “откручивают” value stack до
запомненного значения глубины и делают goto на указанный
адрес в байткоде
Давайте посмотрим как это выглядит
“Внутренности” CPython, часть вторая 14
42. ceval.c, BREAK
case BREAK_LOOP:
why = WHY_BREAK;
goto fast_block_end;
fast_block_end:
while (why != WHY_NOT && f->f_iblock > 0) {
PyTryBlock *b = PyFrame_BlockPop(f);
if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
why = WHY_NOT;
JUMPTO(b->b_handler);
break; }
“Внутренности” CPython, часть вторая 17
43. Другие хранилища
Помимо value stack и block stack, нужно еще хранить адреса
возврата, локальные переменные, лексическое замыкание
“Внутренности” CPython, часть вторая 18
44. Другие хранилища
Помимо value stack и block stack, нужно еще хранить адреса
возврата, локальные переменные, лексическое замыкание
В прошлой части этой презентации рассказывалось, что
локальные переменные определяются на этапе компиляции и для
них заводится список фиксированного размера с обращением по
индексу за O(1)
“Внутренности” CPython, часть вторая 18
45. Другие хранилища
Помимо value stack и block stack, нужно еще хранить адреса
возврата, локальные переменные, лексическое замыкание
В прошлой части этой презентации рассказывалось, что
локальные переменные определяются на этапе компиляции и для
них заводится список фиксированного размера с обращением по
индексу за O(1)
Аналогично компилятор (в байткод) поступает и с лексическим
замыканием
“Внутренности” CPython, часть вторая 18
46. Другие хранилища
Помимо value stack и block stack, нужно еще хранить адреса
возврата, локальные переменные, лексическое замыкание
В прошлой части этой презентации рассказывалось, что
локальные переменные определяются на этапе компиляции и для
них заводится список фиксированного размера с обращением по
индексу за O(1)
Аналогично компилятор (в байткод) поступает и с лексическим
замыканием
Кроме того, code object может вызвать сам себя (рекурсия?) и
тогда обоим экземплярам нужны свои стеки.
“Внутренности” CPython, часть вторая 18
48. frame object
Является хранилищем состояния для конкретного code object
Без code object не имеет смысла - все его параметры
определяются атрибутами исполняемого кода
“Внутренности” CPython, часть вторая 19
49. frame object
Является хранилищем состояния для конкретного code object
Без code object не имеет смысла - все его параметры
определяются атрибутами исполняемого кода
Включает в себя value stack, block stack, locals, cellvars
“Внутренности” CPython, часть вторая 19
50. frame object
Является хранилищем состояния для конкретного code object
Без code object не имеет смысла - все его параметры
определяются атрибутами исполняемого кода
Включает в себя value stack, block stack, locals, cellvars
Ссылается на предыдущий фрейм в стеке
“Внутренности” CPython, часть вторая 19
51. frame object
frame object - полный аналог activation frame в С
“Внутренности” CPython, часть вторая 20
52. frame object
frame object - полный аналог activation frame в С
Только в C activation frame расположен на общем стеке - адреса
возврата и локальные переменные идут “вперемешку”, и activation
frame волей-неволей уничтожается при выходе из функции
(последующие вызовы других функций его перетрут).
“Внутренности” CPython, часть вторая 20
53. frame object
frame object - полный аналог activation frame в С
Только в C activation frame расположен на общем стеке - адреса
возврата и локальные переменные идут “вперемешку”, и activation
frame волей-неволей уничтожается при выходе из функции
(последующие вызовы других функций его перетрут).
В frame object есть ссылка f_back на предыдущий фрейм в стеке,
поэтому в CPython стек вызовов реализован как односвязный
список.
“Внутренности” CPython, часть вторая 20
54. Почему односвязный список?
В отличие от contiguous представления стека в C, это позволяет
продлевать время жизни фрейма сколь угодно долго.
“Внутренности” CPython, часть вторая 21
55. Почему односвязный список?
В отличие от contiguous представления стека в C, это позволяет
продлевать время жизни фрейма сколь угодно долго.
Каждый фрейм выделяется и освобождается как обычный
PyObject, в подходящем месте в памяти, совершенно
необязательно кого-то перетирать.
“Внутренности” CPython, часть вторая 21
56. Почему односвязный список?
В отличие от contiguous представления стека в C, это позволяет
продлевать время жизни фрейма сколь угодно долго.
Каждый фрейм выделяется и освобождается как обычный
PyObject, в подходящем месте в памяти, совершенно
необязательно кого-то перетирать.
Это позволяет реализовать генераторы.
“Внутренности” CPython, часть вторая 21
57. Генераторы
Это просто стек (в виде списка) фреймов, завернутый в объект
(genobject).
“Внутренности” CPython, часть вторая 22
58. Генераторы
Это просто стек (в виде списка) фреймов, завернутый в объект
(genobject).
yield приостанавливает его выполнение, next() возобновляет c
того места, где в последний раз был вызван yield
“Внутренности” CPython, часть вторая 22
59. Генераторы
Это просто стек (в виде списка) фреймов, завернутый в объект
(genobject).
yield приостанавливает его выполнение, next() возобновляет c
того места, где в последний раз был вызван yield
Стек фреймов живет столько, сколько живет genobject.
“Внутренности” CPython, часть вторая 22
62. Генераторы
Генераторы в CPython не столь общи, как хотелось бы
Во-первых, yield это statement, а не expression
“Внутренности” CPython, часть вторая 24
63. Генераторы
Генераторы в CPython не столь общи, как хотелось бы
Во-первых, yield это statement, а не expression
Во-вторых, yield возможен только в самой функции-генераторе,
нo не в вызываемых ею
“Внутренности” CPython, часть вторая 24
64. Генераторы
Генераторы в CPython не столь общи, как хотелось бы
Во-первых, yield это statement, а не expression
Во-вторых, yield возможен только в самой функции-генераторе,
нo не в вызываемых ею
Оба ограничения нефундаментальны
“Внутренности” CPython, часть вторая 24
65. Генераторы - альтернативы
PEP 380 - официально принятый для Python 3 пропоузал,
который позволит реализовывать полноценные сопрограммы на
генераторах (как, например, в Lua)
“Внутренности” CPython, часть вторая 25
66. Генераторы - альтернативы
PEP 380 - официально принятый для Python 3 пропоузал,
который позволит реализовывать полноценные сопрограммы на
генераторах (как, например, в Lua)
greenlet - нативный модуль расширения для CPython,
реализующий кооперативную многозадачность через “срезание”
сишного стека
“Внутренности” CPython, часть вторая 25
67. Генераторы - альтернативы
PEP 380 - официально принятый для Python 3 пропоузал,
который позволит реализовывать полноценные сопрограммы на
генераторах (как, например, в Lua)
greenlet - нативный модуль расширения для CPython,
реализующий кооперативную многозадачность через “срезание”
сишного стека
stackless - форк CPython, развивающий идею “фреймы как
объекты” до полноценной кооперативной многозадачности
“Внутренности” CPython, часть вторая 25
69. Объектная модель
Ранее мы уже упоминали, что все с точки зрения CPython есть
PyObject
“Внутренности” CPython, часть вторая 27
70. Объектная модель
Ранее мы уже упоминали, что все с точки зрения CPython есть
PyObject
Также мы знаем, что в байткоде инструкция BINARY_ADD удаляет
два объекта с вершины стека, складывает их, после чего
помещает результат на вершину стека
“Внутренности” CPython, часть вторая 27
71. Объектная модель
Ранее мы уже упоминали, что все с точки зрения CPython есть
PyObject
Также мы знаем, что в байткоде инструкция BINARY_ADD удаляет
два объекта с вершины стека, складывает их, после чего
помещает результат на вершину стека
Сложение для чисел - это сложение, для строк - конкатенация, и
вообще мы можем переопределить __add__ у чего угодно
“Внутренности” CPython, часть вторая 27
72. Объектная модель
Ранее мы уже упоминали, что все с точки зрения CPython есть
PyObject
Также мы знаем, что в байткоде инструкция BINARY_ADD удаляет
два объекта с вершины стека, складывает их, после чего
помещает результат на вершину стека
Сложение для чисел - это сложение, для строк - конкатенация, и
вообще мы можем переопределить __add__ у чего угодно
Как интерпретатор узнает, что делать?
“Внутренности” CPython, часть вторая 27
73. Type object, слоты, протоколы
В самом начале мы упомянули, что у PyObject обязательно
присутствует поле ob_type - ссылка на type object
“Внутренности” CPython, часть вторая 28
74. Type object, слоты, протоколы
В самом начале мы упомянули, что у PyObject обязательно
присутствует поле ob_type - ссылка на type object
Оказывается, в type object есть слоты, возвращающие реализации
требуемых операций (если те поддерживаются объектом, само
собой)
“Внутренности” CPython, часть вторая 28
75. Type object, слоты, протоколы
В самом начале мы упомянули, что у PyObject обязательно
присутствует поле ob_type - ссылка на type object
Оказывается, в type object есть слоты, возвращающие реализации
требуемых операций (если те поддерживаются объектом, само
собой)
Интерпретатор просто вызывает нужные функции по указателям,
либо возвращает ошибку несовместимости типов.
“Внутренности” CPython, часть вторая 28
76. Type object, слоты, протоколы
В самом начале мы упомянули, что у PyObject обязательно
присутствует поле ob_type - ссылка на type object
Оказывается, в type object есть слоты, возвращающие реализации
требуемых операций (если те поддерживаются объектом, само
собой)
Интерпретатор просто вызывает нужные функции по указателям,
либо возвращает ошибку несовместимости типов.
Рассмотрим на примере int object.
“Внутренности” CPython, часть вторая 28
77. ceval.c
case BINARY_ADD:
w = POP();
v = TOP();
....
else {
slow_add:
x = PyNumber_Add(v, w);
}
...
SET_TOP(x);
“Внутренности” CPython, часть вторая 29
79. abstract.c
static PyObject
*binary_op1(PyObject *v, PyObject *w, const int op_slot)
{
binaryfunc slotv = NULL;
...
slotv = NB_BINOP(v->ob_type->tp_as_number, op_slot);
...
if (slotv) {
x = slotv(v, w);
if (x != Py_NotImplemented)
return x;
“Внутренности” CPython, часть вторая 31
80. intobject.c
static PyObject *
int_add(PyIntObject *v, PyIntObject *w)
{
register long a, b, x;
CONVERT_TO_LONG(v, a);
CONVERT_TO_LONG(w, b);
....
x = (long)((unsigned long)a + b);
if ((x^a) >= 0 || (x^b) >= 0)
return PyInt_FromLong(x);
....
“Внутренности” CPython, часть вторая 32
81. Слоты и протоколы
Слоты и протоколы - это довольно низкоуровневая особенность,
которая из кода на Python не видна
“Внутренности” CPython, часть вторая 33
82. Слоты и протоколы
Слоты и протоколы - это довольно низкоуровневая особенность,
которая из кода на Python не видна
Как она сочетается с наследованием и возможностью
переопределения через “магические” имена (вроде __add__?)
“Внутренности” CPython, часть вторая 33
83. Слоты и протоколы
Слоты и протоколы - это довольно низкоуровневая особенность,
которая из кода на Python не видна
Как она сочетается с наследованием и возможностью
переопределения через “магические” имена (вроде __add__?)
В этом проявляется красота объектной модели Python.
“Внутренности” CPython, часть вторая 33
84. type object == class
type object и классы - одно и то же.
“Внутренности” CPython, часть вторая 34
85. type object == class
type object и классы - одно и то же.
(За исключением old style classes, которые все являются
объектами типа instance и фактически существуют только ради
совместимости с legacy кодом на Python < 2.5)
“Внутренности” CPython, часть вторая 34
86. type object == class
type object и классы - одно и то же.
(За исключением old style classes, которые все являются
объектами типа instance и фактически существуют только ради
совместимости с legacy кодом на Python < 2.5)
При создании класса создается новый type object, который
наполняется содержимым из классов-родителей.
“Внутренности” CPython, часть вторая 34
87. type object == class
При наследовании классов все слоты type object копируются в
новый класс (typeobject.c):
static PyObject *
type_new(PyTypeObject *metatype,
PyObject *args, PyObject *kwds)
{
...
/* Put the proper slots in place */
fixup_slot_dispatchers(type);
“Внутренности” CPython, часть вторая 35
88. type object == class
При попытке переопределить метод в рантайме через выставление
“магического” атрибута класса в дело включается type_setattro,
определенный в слоте tp_setattro у типа класса
“Внутренности” CPython, часть вторая 36
89. type object == class
При попытке переопределить метод в рантайме через выставление
“магического” атрибута класса в дело включается type_setattro,
определенный в слоте tp_setattro у типа класса
Да, у type object тоже есть тип, и это тоже type object :)
“Внутренности” CPython, часть вторая 36
90. type object == class
При попытке переопределить метод в рантайме через выставление
“магического” атрибута класса в дело включается type_setattro,
определенный в слоте tp_setattro у типа класса
Да, у type object тоже есть тип, и это тоже type object :)
type_setattro знает про слоты и правильно их обновляет:
static int
type_setattro(PyTypeObject *type,
PyObject *name, PyObject *value)
{
...
return update_slot(type, name);
“Внутренности” CPython, часть вторая 36
91. type object == class
Кроме того, технические поля инстанса (вроде __dict__), также
являются с точки зрения типа обычными аттрибутами
“Внутренности” CPython, часть вторая 37
92. type object == class
Кроме того, технические поля инстанса (вроде __dict__), также
являются с точки зрения типа обычными аттрибутами
Вкупе с дескрипторами, про которые много кто уже здесь
говорил, это дает поистине впечатляющие возможности
“Внутренности” CPython, часть вторая 37
93. type object == class
Кроме того, технические поля инстанса (вроде __dict__), также
являются с точки зрения типа обычными аттрибутами
Вкупе с дескрипторами, про которые много кто уже здесь
говорил, это дает поистине впечатляющие возможности
В частности, практически ко всему, во что можно впихнуть
заголовок PyObject, можно предоставить идиоматичную
питоновскую обертку, не меняя представления
“Внутренности” CPython, часть вторая 37
94. type object == class
Кроме того, технические поля инстанса (вроде __dict__), также
являются с точки зрения типа обычными аттрибутами
Вкупе с дескрипторами, про которые много кто уже здесь
говорил, это дает поистине впечатляющие возможности
В частности, практически ко всему, во что можно впихнуть
заголовок PyObject, можно предоставить идиоматичную
питоновскую обертку, не меняя представления
Это обуславливает такое количество библиотек - делать
интерфейсы для C/C++ библиотек довольно легко
“Внутренности” CPython, часть вторая 37
95. slots
Чтобы поменять представление объекта в памяти, необязательно
лезть на уровень Python/C API
“Внутренности” CPython, часть вторая 38
96. slots
Чтобы поменять представление объекта в памяти, необязательно
лезть на уровень Python/C API
Для объектов, которые создаются миллионами и редко меняют
набор полей, есть встроенная возможность избежать создания
__dict__ для каждого инстанса.
“Внутренности” CPython, часть вторая 38
97. slots
Чтобы поменять представление объекта в памяти, необязательно
лезть на уровень Python/C API
Для объектов, которые создаются миллионами и редко меняют
набор полей, есть встроенная возможность избежать создания
__dict__ для каждого инстанса.
Для этого в классе надо объявить атрибут __slots__, и в нем
объявить имена полей.
“Внутренности” CPython, часть вторая 38
98. slots
Чтобы поменять представление объекта в памяти, необязательно
лезть на уровень Python/C API
Для объектов, которые создаются миллионами и редко меняют
набор полей, есть встроенная возможность избежать создания
__dict__ для каждого инстанса.
Для этого в классе надо объявить атрибут __slots__, и в нем
объявить имена полей.
Тогда все инстансы будут хранить только ссылки на значения
атрибутов в определенном порядке, __dict__ будет создан только
при попытке выставить атрибут не из __slots__.
“Внутренности” CPython, часть вторая 38
99. slots
>>> class SlotClass: __slots__ = "x", "y"
>>> X = SlotClass()
>>> X.x = 1; X.y = 2
>>> asizeof(X)
128
>>> class DictClass: pass
>>> Y = DictClass()
>>> Y.x = 1; Y.y = 2
>>> asizeof(Y)
480
“Внутренности” CPython, часть вторая 39
101. Заключение
Напоследок хочу напомнить, что все рассказанное может быть
почерпнуто прямо из исходников CPython - они хорошо
задокументированы и читаются довольно легко
“Внутренности” CPython, часть вторая 41
102. Заключение
Напоследок хочу напомнить, что все рассказанное может быть
почерпнуто прямо из исходников CPython - они хорошо
задокументированы и читаются довольно легко
Лезть в исходники интерпретатора чтобы понять, как ведет себя
прикладной код - вполне себе реальный сценарий
“Внутренности” CPython, часть вторая 41
103. Заключение
Напоследок хочу напомнить, что все рассказанное может быть
почерпнуто прямо из исходников CPython - они хорошо
задокументированы и читаются довольно легко
Лезть в исходники интерпретатора чтобы понять, как ведет себя
прикладной код - вполне себе реальный сценарий
Если тема доклада вас заинтересовала, то от себя хочу
порекомендовать следующие ресурсы
“Внутренности” CPython, часть вторая 41
104. “Python Innards”, Yaniv Aknin
http://en.wordpress.com/tag/pythons-innards/
Серия постов от Yaniv Aknin про байткод, стеки,
thread state и объектную модель в CPython.
“Внутренности” CPython, часть вторая 42
105. “Python internals”, Eli Bendersky
http://tinyurl.com/pyinternals
Серия постов от Eli Bendersky про компилятор
Python, а также объектную модель Python 3.
“Внутренности” CPython, часть вторая 43
106. “Understanding the Python GIL”, David Beazley
http://www.dabeaz.com/GIL/
Презентации David Beazley, касающиеся
особенностей реализации GIL в Python,
проливающие свет на его неочевидное поведение
(например, существенное замедление CPU-bound
многопоточного кода на многоядерных машинах)
“Внутренности” CPython, часть вторая 44