- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
104 Лексический анализ и парсинг с использованием синтаксических деревьев
в Parser Python.asdl, а доступ к ним предоставляется через модуль ast в стандартной библиотеке.
Если у выражения или команды есть дочерние узлы, то соответствующая дочерняя функция ast_for_*() будет вызываться в порядке обхода в глубину.
ВАЖНЫЕ ТЕРМИНЫ
Ниже перечислены некоторые ключевые термины, встретившиеся в этой главе:
zz Абстрактное синтаксическое дерево (AST): представление грамматики и команд Python в виде контекстного дерева.
zz Конкретное синтаксическое дерево (CST): представление лексем и символических имен в виде неконтекстного дерева.
zz Дерево разбора (parse tree): другой термин для обозначения конкретного синтаксического дерева.
zz Лексема (token): тип символического имени (например, +).
zz Токенизация (tokenization): процесс преобразования текста в лексемы.
zz Парсинг (parsing): процесс преобразования текста в CST или AST.
ПРИМЕР: ДОБАВЛЕНИЕ ОПЕРАТОРА «ПОЧТИ РАВНО»
Чтобы объединить все сказанное, мы добавим в язык Python новый элемент синтаксиса и перекомпилируем CPython, чтобы он этот синтаксис поддерживал.
Оператор сравнения сравнивает два и более значений:
>>>a = 1
>>>b = 2
>>>a == b False
Книги для программистов: https://t.me/booksforits
Пример: добавление оператора «почти равно» 105
Операторы, используемые в выражениях сравнения, называются опера торами сравнения. Вероятно, вам знакомы следующие операторы сравнения:
zz Меньше: < zz Больше: > zz Равно: ==
zz Не равно: !=
СМ. ТАКЖЕ
Расширенные сравнения в модели данных были предложены для Python 2.1 в PEP 207. PEP содержит контекст, историю и обоснование для реализации методов сравнения в нестандартных типах Python.
Добавим еще один оператор сравнения «почти равно», который будет представляться символами ~=. Оператор будет обладать следующим поведением:
zz Число с плавающей точкой при сравнении с целым числом сначала будет приводиться к целочисленному типу.
zz При сравнении двух целых чисел будут использоваться обычные операторы проверки равенства.
Новый оператор должен возвращать следующий результат в REPL:
>>>1 ~= 1
True
>>>1 ~= 1.0
True
>>>1 ~= 1.01
True
>>>1 ~= 1.9 False
Чтобы добавить новый оператор, необходимо сначала обновить грамматику CPython. В файле Grammar python.gram операторы сравнения определяются символическим именем comp_op:
Книги для программистов: https://t.me/booksforits
106 Лексический анализ и парсинг с использованием синтаксических деревьев
comparison[expr_ty]:
| a=bitwise_or b=compare_op_bitwise_or_pair+ ...
| bitwise_or
compare_op_bitwise_or_pair[CmpopExprPair*]:
| eq_bitwise_or
| noteq_bitwise_or
| lte_bitwise_or
| lt_bitwise_or
| gte_bitwise_or
| gt_bitwise_or
| notin_bitwise_or
| in_bitwise_or
| isnot_bitwise_or
| is_bitwise_or
eq_bitwise_or[CmpopExprPair*]: '==' a=bitwise_or ...
noteq_bitwise_or[CmpopExprPair*]:
| (tok='!=' {_PyPegen_check_barry_as_flufl(p) ? NULL : tok}) ...
lte_bitwise_or[CmpopExprPair*]: '<=' a=bitwise_or ...
lt_bitwise_or[CmpopExprPair*]: '<' a=bitwise_or ...
gte_bitwise_or[CmpopExprPair*]: '>=' a=bitwise_or ...
gt_bitwise_or[CmpopExprPair*]: '>' a=bitwise_or ...
notin_bitwise_or[CmpopExprPair*]: 'not' 'in' a=bitwise_or ...
in_bitwise_or[CmpopExprPair*]: 'in' a=bitwise_or ...
isnot_bitwise_or[CmpopExprPair*]: 'is' 'not' a=bitwise_or ...
is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or ...
Измените выражение compare_op_bitwise_or_pair, чтобы оно также допускало новую пару ale_bitwise_or:
compare_op_bitwise_or_pair[CmpopExprPair*]: | eq_bitwise_or
...
| ale_bitwise_or
Определите новое выражение ale_bitwise_or под существующим выражением is_bitwise_or:
...
is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or ...
ale_bitwise_or[CmpopExprPair*]: '~=' a=bitwise_or
{ _PyPegen_cmpop_expr_pair(p, AlE, a) }
Новый тип определяет именованное выражение ale_bitwise_or, которое содержит терминал '~='.
Книги для программистов: https://t.me/booksforits
Пример: добавление оператора «почти равно» 107
Вызов функции _PyPegen_cmpop_expr_pair(p, AlE, a) является выражением для получения узла cmpop из AST. Название типа AlE происходит от слов «almost equal» («почти равно»).
Затем добавьте лексему в Grammar Tokens:
ATEQUAL |
'@=' |
RARROW |
'->' |
ELLIPSIS |
'...' |
COLONEQUAL |
':=' |
# Добавьте эту строку |
|
ALMOSTEQUAL |
'~=' |
Чтобы обновить грамматику и лексемы в C, необходимо заново сгенерировать заголовки.
В macOS и Linux используется следующая команда:
$ make regen-token regen-pegen
ВWindows выполните следующую команду из каталога PCBuild:
>build.bat --regen
Эти действия автоматически обновляют токенизатор. Например, откройте файл Parser/token.c и посмотрите, как изменилась секция case в функции
PyToken_TwoChars():
case '~':
switch (c2) {
case '=': return ALMOSTEQUAL;
}
break;
}
Если перекомпилировать CPython на этом этапе и открыть REPL, вы увидите, что токенизатор успешно распознает лексему, но AST не знает, как обработать ее:
$ ./python
>>> 1 ~= 2
SystemError: invalid comp_op: ~=
Книги для программистов: https://t.me/booksforits
108 Лексический анализ и парсинг с использованием синтаксических деревьев
Исключение поднимается функцией ast_for_comp_op() из файла Python ast.c, потому что ALMOSTEQUAL не распознается как допустимый оператор для коман ды сравнения.
Compare — тип выражения, определенный в Parser Python.asdl. Он содержит свойства для левого выражения, список операторов ops и список сравниваемых выражений comparators:
| Compare(expr left, cmpop* ops, expr* comparators)
Внутри определения Compare находится ссылка на перечисление cmpop:
cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn
Это список возможных листовых узлов AST, которые могут использоваться как операторы сравнения. Наш оператор в списке отсутствует, его необходимо добавить. Включите в этот список новый тип AlE:
cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn | AlE
Затем снова сгенерируйте AST, чтобы обновить заголовочные файлы C AST:
$ make regen-ast
Команда обновляет список операторов сравнения (_cmpop) в файле Include/ Python-ast.h и включает в него вариант AlE:
typedef enum _cmpop { Eq=1, NotEq=2, Lt=3, LtE=4, Gt=5, GtE=6, Is=7,
IsNot=8, In=9, NotIn=10, AlE=11 } cmpop_ty;
AST не знает, что лексема ALMOSTEQUAL эквивалентна оператору сравнения AlE. А значит, необходимо обновить код C для AST.
Перейдите к функции ast_for_comp_op() в Python ast.c. Найдите команду switch для лексем операторов. Она возвращает одно из списка значений _cmpop.
Добавьте две строки для обнаружения лексемы ALMOSTEQUAL и возвращения оператора сравнения AlE:
Python ast.c, строка 1222
static cmpop_ty
ast_for_comp_op(struct compiling *c, const node *n)
{
/* comp_op: '<'|'>'|'=='|'>='|'<='|'!='|'in'|'not' 'in'|'is' |'is' 'not'
Книги для программистов: https://t.me/booksforits