
- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода CPython
- •Что в исходном коде?
- •Настройка среды разработки
- •IDE или редактор?
- •Настройка Visual Studio
- •Настройка Visual Studio Code
- •Настройка Vim
- •Выводы
- •Компиляция CPython
- •Компиляция CPython на macOS
- •Компиляция CPython на Linux
- •Установка специализированной версии
- •Знакомство с Make
- •Make-цели CPython
- •Компиляция CPython на Windows
- •Профильная оптимизация
- •Выводы
- •Грамматика и язык Python
- •Спецификация языка Python
- •Генератор парсеров
- •Повторное генерирование грамматики
- •Выводы
- •Конфигурация и ввод
- •Конфигурация состояния
- •Структура данных конфигурации среды выполнения
- •Конфигурация сборки
- •Сборка модуля из входных данных
- •Выводы
- •Генерирование конкретного синтаксического дерева
- •Парсер/токенизатор CPython
- •Абстрактные синтаксические деревья
- •Важные термины
- •Пример: добавление оператора «почти равно»
- •Выводы
- •Компилятор
- •Исходные файлы
- •Важные термины
- •Создание экземпляра компилятора
- •Флаги будущей функциональности и флаги компилятора
- •Таблицы символических имен
- •Основная компиляция
- •Ассемблер
- •Создание объекта кода
- •Использование Instaviz для вывода объекта кода
- •Пример: реализация оператора «почти равно»
- •Выводы
- •Цикл вычисления
- •Исходные файлы
- •Важные термины
- •Построение состояния потока
- •Построение объектов кадров
- •Выполнение кадра
- •Стек значений
- •Пример: добавление элемента в список
- •Выводы
- •Управление памятью
- •Выделение памяти в C
- •Проектирование системы управления памятью Python
- •Аллокаторы памяти CPython
- •Область выделения объектной памяти и PyMem
- •Область выделения сырой памяти
- •Нестандартные области выделения памяти
- •Санитайзеры выделенной памяти
- •Арена памяти PyArena
- •Подсчет ссылок
- •Сборка мусора
- •Выводы
- •Параллелизм и конкурентность
- •Модели параллелизма и конкурентности
- •Структура процесса
- •Многопроцессорный параллелизм
- •Многопоточность
- •Асинхронное программирование
- •Генераторы
- •Сопрограммы
- •Асинхронные генераторы
- •Субинтерпретаторы
- •Выводы
- •Объекты и типы
- •Примеры этой главы
- •Встроенные типы
- •Типы объектов
- •Тип type
- •Типы bool и long
- •Тип строки Юникода
- •Словари
- •Выводы
- •Стандартная библиотека
- •Модули Python
- •Модули Python и C
- •Набор тестов
- •Запуск набора тестов в Windows
- •Запуск набора тестов в Linux или macOS
- •Флаги тестирования
- •Запуск конкретных тестов
- •Модули тестирования
- •Вспомогательные средства тестирования
- •Выводы
- •Отладка
- •Обработчик сбоев
- •Компиляция поддержки отладки
- •LLDB для macOS
- •Отладчик Visual Studio
- •Отладчик CLion
- •Выводы
- •Бенчмаркинг, профилирование и трассировка
- •Использование timeit для микробенчмарка
- •Использование набора тестов производительности Python
- •Профилирование кода Python с использованием cProfile
- •Выводы
- •Что дальше?
- •Создание расширений C для CPython
- •Улучшение приложений Python
- •Участие в проекте CPython
- •Дальнейшее обучение
- •Препроцессор C
- •Базовый синтаксис C
- •Выводы
- •Благодарности

Использование Instaviz для вывода объекта кода 133
ИСПОЛЬЗОВАНИЕ INSTAVIZ ДЛЯ ВЫВОДА ОБЪЕКТА КОДА
Все стадии работы компилятора можно собрать воедино с помощью модуля instaviz:
import instaviz
def foo(): a = 2**4
b = 1 + 5
c = [1, 4, 6] for i in c:
print(i)
else:
print(a) return c
instaviz.show(foo)
Этот фрагмент построит большое и сложное дерево графа AST. Инструкции байт-кода можно просмотреть по порядку:
Объект кода с именами переменных, константами и двоичным значением co_code:
Книги для программистов: https://t.me/booksforits

134 Компилятор
Опробуйте эту возможность с другим, более сложным кодом, чтобы больше узнать о компиляторе CPython и объектах кода.
ПРИМЕР: РЕАЛИЗАЦИЯ ОПЕРАТОРА «ПОЧТИ РАВНО»
Теперь, после рассмотрения компилятора, инструкций байт-кода и ассемблера, мы сможем изменить CPython и добавить поддержку оператора «почти равно», который был скомпилирован в грамматику в предыдущей главе.
Сначала необходимо добавить внутреннее определение #define для оператора Py_AlE, чтобы на него можно было ссылаться внутри расширенных функций сравнения для PyObject.
Откройте файл Include object.h и найдите следующие команды #define:
/* Коды операций расширенного сравнения */
#define Py_LT 0 #define Py_LE 1 #define Py_EQ 2 #define Py_NE 3
#define Py_GT 4 #define Py_GE 5
Книги для программистов: https://t.me/booksforits

Пример: реализация оператора «почти равно» 135
Добавим дополнительное значение PyAlE со значением 6:
/* Новый оператор сравнения "почти равно" */
#define Py_AlE 6
Прямо под этим выражением располагается макрос Py_RETURN_RICHCOMPARE. Обновите этот макрос и добавьте в него условие case для Py_AlE:
/*
*Макрос для реализации расширенного сравнения
*
*Необходим макрос, потому что может использоваться
*любой C-совместимый тип
*/
#define Py_RETURN_RICHCOMPARE(val1, val2, op) \ do { \
switch (op) { \
case Py_EQ: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_NE: if ((val1) != (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_LT: if ((val1) < (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_GT: if ((val1) > (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_LE: if ((val1) <= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_GE: if ((val1) >= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
/* + */ case Py_AlE: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE;\ default: \
Py_UNREACHABLE(); \
}\
}while (0)
Вфайле Objects object.c присутствует проверка того, что оператор лежит в диапазоне от 0 до 5. Так как вы добавили значение 6, проверку необходимо обновить:
Objects object.c, строка 709
PyObject *
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
{
PyThreadState *tstate = _PyThreadState_GET();
assert(Py_LT <= op && op <= Py_GE);
Замените последнюю строку следующей:
assert(Py_LT <= op && op <= Py_AlE);
Книги для программистов: https://t.me/booksforits

136 Компилятор
Затем необходимо обновить код операции COMPARE_OP для поддержки Py_AlE как значения для типа оператора.
Сначала отредактируйте файл Objects object.c и добавьте Py_AlE в список _Py_SwappedOp. Этот список используется для определения того, содержит ли пользовательский класс только один магический (dunder) метод оператора, но не содержит другого.
Например, если вы определили класс Coordinate, оператор равенства можно определить реализацией магического метода __eq__:
class Coordinate:
def __init__(self, x, y): self.x = x
self.y = y
def __eq__(self, other):
if isinstance(other, Coordinate):
return (self.x == other.x and self.y == other.y) return super(self, other).__eq__(other)
И хотя вы не реализовали __ne__ (не равно, not equal) для Coordinate, CPython предполагает, что может применяться оператор, обратный __eq__:
>>> Coordinate(1, 100) != Coordinate(2, 400) True
В файле Objects object.c найдите список _Py_SwappedOp и добавьте Py_AlE в его конец. Затем добавьте "~=" в конец списка opstrings:
int _Py_SwappedOp[] = {Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE, Py_AlE};
static const char * const opstrings[]
= {"<", "<=", "==", "!=", ">", ">=", "~="};
Откройте файл Lib/opcode.py и отредактируйте кортеж операторов расширенного сравнения:
cmp_op = ('<', '<=', '==', '!=', '>', '>=')
Добавьте новый оператор в конец кортежа:
cmp_op = ('<', '<=', '==', '!=', '>', '>=', '~=')
Список opstrings используется для сообщений об ошибках, если операторы расширенного сравнения не реализованы для класса.
Книги для программистов: https://t.me/booksforits

Пример: реализация оператора «почти равно» 137
Теперь можно обновить компилятор для обработки условия в свойстве PyCmp_AlE в узле BinOp. Откройте файл Python compile.c и найдите compiler_ addcompare():
Python compile.c, строка 2479
static int compiler_addcompare(struct compiler *c, cmpop_ty op)
{
int cmp; switch (op) { case Eq:
cmp = Py_EQ; break;
case NotEq:
cmp = Py_NE; break;
case Lt:
cmp = Py_LT; break;
case LtE:
cmp = Py_LE; break;
case Gt:
cmp = Py_GT; break;
case GtE:
cmp = Py_GE; break;
Добавьте еще один оператор case в команду switch, чтобы сопоставить comp_op AlE дерева абстрактного синтаксиса с кодом операции сравнения PyCmp_AlE:
...
case AlE:
cmp = Py_AlE; break;
Теперь можно запрограммировать поведение оператора «почти равно» по следующему сценарию:
zz 1 ~= 2 — False;
zz 1 ~= 1.01 — True с округлением вниз.
Чтобы добиться нужного результата, придется написать дополнительный код. Пока можно привести оба числа с плавающей точкой к целым числам и сравнить их.
Книги для программистов: https://t.me/booksforits

138 Компилятор
API CPython содержит много функций для работы с типами PyLong (int) и PyFloat (float). Эта тема будет рассмотрена в главе «Объекты и типы».
Найдите функцию float_richcompare() в Objects floatobject.c и добавьте в определение Compare: goto следующее условие case:
Objects floatobject.c, строка 358
static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
...
case Py_GT:
r = i > j; break;
/* НАЧАЛО нового кода */ case Py_AlE: {
double diff = fabs(i - j);
double rel_tol = 1e-9; // Относительная погрешность double abs_tol = 0.1; // Абсолютная погрешность
r = (((diff <= fabs(rel_tol * j)) || (diff <= fabs(rel_tol * i))) || (diff <= abs_tol));
}
break;
}
/* КОНЕЦ нового кода */ return PyBool_FromLong(r);
Этот код обрабатывает сравнение чисел с плавающей точкой при использовании оператора «почти равно». В нем используется логика, сходная с логикой math.isclose(), определенной в PEP 485, но с жестко закодированной абсолютной погрешностью 0.1.
Также необходимо внести изменения еще в одну защитную проверку, которая находится в цикле вычисления Python ceval.c. Цикл вычисления рассматривается в следующей главе.
Найдите следующий фрагмент кода:
...
case TARGET(COMPARE_OP): { assert(oparg <= Py_GE);
Замените проверку следующей:
assert(oparg <= Py_AlE);
Книги для программистов: https://t.me/booksforits