- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
114 Компилятор
zz В качестве арены выделения памяти компилятора присваивается та, которая использовалась интерпретатором. За дополнительной информацией о распределителях памяти обращайтесь к разделу «Специальные распределители памяти».
zz Все флаги будущей функциональности задаются перед компиляцией кода.
ФЛАГИ БУДУЩЕЙ ФУНКЦИОНАЛЬНОСТИ И ФЛАГИ КОМПИЛЯТОРА
Для включения функциональности внутри компилятора используются флаги двух типов: флаги будущей функциональности и флаги компилятора. Эти флаги могут задаваться в двух местах:
1.В конфигурации состояния, содержащей переменные среды и флаги командной строки.
2.В исходном коде модуля с использованием команд __future__.
За дополнительной информацией о конфигурации состояния обращайтесь к разделу «Конфигурация состояния» главы «Конфигурация и ввод».
Флаги будущей функциональности
Необходимость в флагах будущей функциональности возникает из-за синтаксиса или возможностей конкретного модуля. Например, в Python 3.7 появилась отложенная обработка аннотаций типов с использованием флага будущей функциональности annotations:
from __future__ import annotations
В коде, следующем за этой командой, могут использоваться неразрешенные аннотации типов, поэтому команда __future__ необходима. В противном случае модуль не будет импортироваться.
Флаги будущей функциональности в 3.9
В версии 3.9 все флаги будущей функциональности, кроме двух (annotations и barry_as_FLUFL), являются обязательными и активизируются автоматически:
Книги для программистов: https://t.me/booksforits
|
Флаги будущей функциональности и флаги компилятора 115 |
|
|
ФЛАГ |
НАЗНАЧЕНИЕ |
absolute_import |
Включение абсолютного импортирования (PEP 328) |
annotations |
Отложенная обработка аннотаций типов (PEP 563) |
barry_as_FLUFL |
Пасхальное яйцо (PEP 401) |
division |
Использование оператора истинного деления (PEP 238) |
generator_stop |
Возможность использования StopIteration внутри генераторов |
|
(PEP 479) |
generators |
Включение простых генераторов (PEP 255) |
nested_scopes |
Добавление статических вложенных областей видимости |
|
(PEP 227) |
print_function |
Использование print как функции (3105) |
unicode_literals |
Хранение литералов str в Юникоде вместо байтов (PEP 3112) |
with_statement |
Включение оператора with (PEP 343) |
ПРИМЕЧАНИЕ
Большинство флагов __future__ использовалось для обеспечения пор тируемости между Python 2 и 3. Вероятно, с приближением выхода Python 4.0 появится больше флагов будущей функциональности.
Флаги компилятора
Флаги компилятора привязаны к установленному окружению, что позволяет им изменять способ выполнения кода или работы компилятора, но они не должны связываться с исходным кодом, как команды __future__.
Один из примеров флагов компилятора — флаг -O1 для оптимизации команд assert. Этот флаг отключает все команды assert, которые могли быть вставлены в код в целях отладки2. Режим также можно включить установкой переменной среды PYTHONOPTIMIZE=1.
1 https://docs.python.org/3/using/cmdline.html#cmdoption-o.
2 https://realpython.com/python-debugging-pdb/.
Книги для программистов: https://t.me/booksforits
116 Компилятор
ТАБЛИЦЫ СИМВОЛИЧЕСКИХ ИМЕН
Перед компиляцией кода создается таблица символических имен, для чего используется функция API PySymtable_BuildObject().
Таблица символических имен предоставляет список пространств имен, глобальных и локальных имен, которые должны использоваться компилятором для ссылок и разрешения областей видимости.
Исходные файлы
Ниже перечислены исходные файлы, относящиеся к таблице символических имен:
ФАЙЛ |
НАЗНАЧЕНИЕ |
Python symtable.c |
Реализация таблицы символических имен |
Include symtable.h |
Определение API таблицы символических имен и типов |
Lib symtable.py |
Модуль стандартной библиотеки symtable |
Структура данных таблицы символических имен
Структура symtable должна быть единственным экземпляром symtable для компилятора, из-за чего пространства имен начинают играть особенно важную роль.
Например, если вы создали метод с именем resolve_names() в одном классе, а потом объявили еще один метод с тем же именем в другом классе, необходимо точно указать, какой из методов будет вызываться внутри модуля.
Структура symtable служит этой цели, а также гарантирует, что переменные, объявленные в узкой области видимости, не будут автоматически становиться глобальными.
Структура таблицы символических имен symtable содержит следующие поля.
ПОЛЕ |
ТИП |
НАЗНАЧЕНИЕ |
recursion_depth |
int |
Текущая глубина рекурсии |
recursion_limit |
int |
Предельная глубина рекурсии до выдачи |
|
|
ошибки RecursionError |
Книги для программистов: https://t.me/booksforits
|
|
Таблицы символических имен 117 |
|
|
|
ПОЛЕ |
ТИП |
НАЗНАЧЕНИЕ |
st_blocks |
PyObject * (dict) |
Соответствие адресов узлов AST и элемен- |
|
|
тов таблицы символических имен |
st_cur |
_symtable_entry |
Текущий элемент таблицы символических |
|
|
имен |
st_filename |
PyObject * (str) |
Имя компилируемого файла |
st_future |
PyFutureFeatures |
Возможности будущей функциональности |
|
|
модуля, влияющие на таблицу символиче- |
|
|
ских имен |
st_global |
PyObject * (dict) |
Ссылки на символические имена в st_top |
st_nblocks |
int |
Количество использованных блоков |
st_private |
PyObject * (str) |
Имя текущего класса (не обязательно) |
st_stack |
PyObject * (list) |
Стек с информацией о пространствах имен |
st_top |
_symtable_entry |
Элемент таблицы символических имен для |
|
|
модуля |
Использование модуля стандартной библиотеки symtable
Доступ к некоторым функциям C API таблицы символических имен предоставляется в Python через модуль symtable стандартной библиотеки.
При помощи другого модуля tabulate (доступного в PyPI) можно создать скрипт для вывода таблицы символических имен.
Таблицы символических имен могут быть вложенными. Если модуль содержит функцию или класс, они тоже будут иметь таблицу символических имен.
Создайте скрипт symviz.py с рекурсивной функцией show():
cpython-book-samples 30 symviz.py
import tabulate import symtable
code = """
def calc_pow(a, b): return a ** b
a = 1
Книги для программистов: https://t.me/booksforits
118 Компилятор
b = 2
c = calc_pow(a,b)
"""
_st = symtable.symtable(code, "example.py", "exec")
def show(table):
print("Symtable {0} ({1})".format(table.get_name(), table.get_type()))
print(
tabulate.tabulate(
[
(
symbol.get_name(), symbol.is_global(), symbol.is_local(), symbol.get_namespaces(),
)
for symbol in table.get_symbols()
],
headers=["name", "global", "local", "namespaces"], tablefmt="grid",
)
)
if table.has_children():
[show(child) for child in table.get_children()] show(_st)
Запустите symviz.py в командной строке, чтобы просмотреть таблицу символических имен для примера:
Книги для программистов: https://t.me/booksforits
Таблицы символических имен 119
Реализация таблицы символических имен
Реализация таблицы символических имен находится в файле Python symtable.c, а первичным интерфейсом является функция PySymtable_BuildObject().
По аналогии с компиляцией AST, рассмотренной в предыдущей главе, PySymtable_BuildObject() переключается между возможными типами mod_ty
(Module, Interactive, Expression и FunctionType) и обходит все содержащиеся в них команды.
Таблица символических имен в рекурсивном режиме анализирует узлы и ветви AST (типа mod_ty) и добавляет элементы в symtable:
Python symtable.c, строка 261
struct symtable *
PySymtable_BuildObject(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
{
struct symtable *st = symtable_new(); asdl_seq *seq;
int i;
PyThreadState *tstate;
int recursion_limit = Py_GetRecursionLimit();
...
st->st_top = st->st_cur; switch (mod->kind) { case Module_kind:
seq = mod->v.Module.body;
for (i = 0; i < asdl_seq_LEN(seq); i++) if (!symtable_visit_stmt(st,
(stmt_ty)asdl_seq_GET(seq, i))) goto error;
break;
case Expression_kind:
...
case Interactive_kind:
...
case FunctionType_kind:
...
}
...
}
PySymtable_BuildObject() перебирает все команды модуля и вызывает symtable_visit_stmt() — по сути, огромную команду switch с условием case для каждого типа команд (определяемого в Parser Python.asdl).
Книги для программистов: https://t.me/booksforits
120 Компилятор
У каждого типа команд имеется соответствующая функция для разрешения символических имен. Например, для команды типа определения функции (FunctionDef_kind) реализована конкретная логика для следующих действий:
zz проверка текущей глубины рекурсии и сравнение ее с предельной глубиной;
zz добавление имени функции в таблицу символических имен, чтобы ее можно было вызывать или передавать как объект функции;
zz разрешение не литеральных аргументов по умолчанию по таблице символических имен;
zz разрешение аннотаций типа;
zz разрешение декораторов функций.
Наконец, symtable_enter_block() посещает блок с содержимым функции. Далее происходит посещение и преобразование аргументов, после чего посещается и обрабатывается тело функции.
ВАЖНО
У вас когда-либо возникал вопрос, почему аргументы по умолчанию в Python изменяемы? Все дело в symtable_visit_stmt().Аргумент по умол чанию представляет собой ссылку на переменную из symtable.
Такой подход позволяет избежать лишней работы по копированию зна чений в неизменяемый тип.
Чтобы вы лучше поняли суть происходящего, ниже приведен код C для построения таблицы символических имен для функции из symtable_visit_stmt().
Python symtable.c, строка 1171
static int
symtable_visit_stmt(struct symtable *st, stmt_ty s)
{
if (++st->recursion_depth > st->recursion_limit) { PyErr_SetString(PyExc_RecursionError,
"maximum recursion depth exceeded during compilation"); VISIT_QUIT(st, 0);
}
switch (s->kind) { case FunctionDef_kind:
if (!symtable_add_def(st, s->v.FunctionDef.name, DEF_LOCAL))
Книги для программистов: https://t.me/booksforits