- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
150 Цикл вычисления
Добавление отсутствующих именованных аргументов
Любые дополнительные именованные аргументы, переданные при вызове функции, добавляются в словарь **kwargs.
Если этот словарь не существует, выдается исключение.
Свертка замыканий
Все имена замыканий добавляются в список свободных имен переменных объекта кода.
Создание генераторов, сопрограмм и асинхронных генераторов
Если вычисляемый объект кода содержит флаг, указывающий на то, что это генератор, сопрограмма или асинхронный генератор, то создается новый кадр с использованием одного из уникальных методов библиотек генераторов, сопрограмм или асинхронных библиотек, а текущий кадр добавляется как свойство.
СМ. ТАКЖЕ
API и реализация генераторов, сопрограмм и асинхронных кадров рас сматриваются в главе «Параллелизм и конкурентность».
Возвращается новый кадр, а исходный кадр не вычисляется. Кадр будет вычисляться только тогда, когда генератор, сопрограмма или асинхронный метод вызываются для выполнения его цели.
Наконец, с новым кадром вызывается _PyEval_EvalFrame().
ВЫПОЛНЕНИЕ КАДРА
Как упоминалось ранее в главах «Лексический анализ и парсинг с использованием синтаксических деревьев» и «Компилятор», объект кода содержит выполняемый байт-код, закодированный в двоичном формате. Также он содержит список переменных и таблицу символических имен.
Книги для программистов: https://t.me/booksforits
Выполнение кадра 151
Локальные и глобальные переменные определяются во время выполнения в зависимости от того, как была вызвана функция, модуль или блок. Эта информация добавляется в кадр функцией_PyEval_EvalCode().
Существуют и другие варианты использования кадров — например, декоратор сопрограмм, который динамически генерирует кадр с целью в качестве переменной.
Функция открытого API PyEval_EvalFrameEx() вызывает функцию вычисления кадра, настроенную для интерпретатора, из свойства eval_frame. Предложение PEP 523 сделало вычисление кадра подключаемым (Python 3.7 и выше).
_PyEval_EvalFrameDefault() — установленная по умолчанию функция вычисления кадра, и это единственный вариант, входящий в комплект поставки CPython.
ПРИМЕЧАНИЕ
При чтении файла Python ceval.c можно заметить, сколько раз в нем ис пользуются макросы C.
Макросы C открывают возможность повторного использования кода без лишних затрат на вызов функций.Компилятор преобразует макросы в код C, а затем компилирует его.
После установки официального расширения C/C++ в Visual Studio Code будет выводиться подстановка макроса:
В CLion выделите макрос и нажмите Alt+пробел, чтобы просмотреть его определение.
Книги для программистов: https://t.me/booksforits
152 Цикл вычисления
Эта центральная функция собирает все воедино и приводит в действие ваш код. В ней воплощены целые десятилетия оптимизации, так что даже одна строка кода может значительно повлиять на производительность CPython в целом.
Все, что выполняется в CPython, проходит через функцию вычисления кадра.
Трассировка выполнения кадра
В Python 3.7 и выше можно пошагово отслеживать выполнение кадров, установив атрибут трассировки для текущего потока. Тип PyFrameObject содержит свойство f_trace типа PyObject *. Предполагается, что значение указывает на функцию Python.
Следующий пример кода выбирает в качестве глобальной функции трассировки функцию с именем my_trace(), которая получает стек из текущего кадра, выводит дизассемблированные коды операций на экран и добавляет информацию для отладки:
cpython-book-samples 31 my_trace.py
import sys import dis import traceback import io
def my_trace(frame, event, args): frame.f_trace_opcodes = True
stack = traceback.extract_stack(frame) pad = " "*len(stack) + "|"
if event == "opcode":
with io.StringIO() as out:
dis.disco(frame.f_code, frame.f_lasti, file=out) lines = out.getvalue().split("\n") [print(f"{pad}{l}") for l in lines]
elif event == "call": print(f"{pad}Calling {frame.f_code}")
elif event == "return": print(f"{pad}Returning {args}")
elif event == "line":
print(f"{pad}Changing line to {frame.f_lineno}") else:
print(f"{pad}{frame} ({event} - {args})") print(f"{pad}----------------------------------") return my_trace
sys.settrace(my_trace)
# Выполнение кода для демонстрации eval('"-".join([letter for letter in "hello"])')
Книги для программистов: https://t.me/booksforits
Стек значений 153
Функция sys.settrace() назначает переданную функцию функцией трассировки по умолчанию для текущего состояния потока. У всех новых кадров, созданных после этого вызова, поле f_trace инициализируется этой функцией.
Следующий фрагмент выводит код из каждого стека и указатель на следующую операцию перед ее выполнением. Если кадр возвращает значение, выводится команда return:
Полный список возможных инструкций байт-кода приведен в документации модуля dis1.
СТЕК ЗНАЧЕНИЙ
Внутри основного цикла вычисления создается стек значений. Стек содержит список указателей на экземпляры PyObject. Это могут быть переменные, ссылки на функции (которые в Python являются объектами) или любые другие объекты Python.
Инструкции байт-кода в цикле вычисления получают входные данные из стека значений.
Пример операции байт-кода: BINARY_OR
Бинарные операции, которые рассматривались в предшествующих главах, компилируются в одну инструкцию.
1 https://docs.python.org/3/library/dis.html#python-bytecode-instructions.
Книги для программистов: https://t.me/booksforits
154 Цикл вычисления
Допустим, вы вставили в Python оператор or:
if left or right: pass
Компилятор преобразует оператор or в инструкцию BINARY_OR:
static int
binop(struct compiler *c, operator_ty op)
{
switch (op) { case Add:
return BINARY_ADD;
...
case BitOr:
return BINARY_OR;
В цикле вычисления секция case для BINARY_OR получает два значения из стека значений (левый и правый операнды), после чего вызывает PyNumber_Or для этих двух объектов:
...
case TARGET(BINARY_OR): {
PyObject *right = POP(); PyObject *left = TOP();
PyObject *res = PyNumber_Or(left, right); Py_DECREF(left);
Py_DECREF(right); SET_TOP(res);
if (res == NULL) goto error;
DISPATCH();
}
Результат (res) назначается вершиной стека, переопределяя текущее верхнее значение.
Моделирование стека значений
Чтобы понять, как работает цикл вычислений, необходимо понимать смысл стека значений.
Стек значений можно представить себе в виде деревянного колышка, на который нанизываются цилиндры. При этом цилиндры можно добавлять или снимать только по одному, и всегда только на вершину стека или с нее.
Книги для программистов: https://t.me/booksforits
Стек значений 155
В CPython для добавления объектов в стек значений используется макрос PUSH(a), где a — указатель на PyObject.
Предположим, вы создали PyLong со значением 10 и занесли его в стек значений:
PyObject *a = PyLong_FromLong(10);
PUSH(a);
Это действие приводит к следующему эффекту:
|
|
|
|
В следующей операции для получения этого значения используется макрос POP()(получение верхнего значения стека):
PyObject *a = POP(); // a - PyLongObject со значением 10
Это действие возвращает верхнее значение, а стек значений остается пустым:
|
|
|
|
Допустим, в стек были добавлены два значения:
PyObject *a = PyLong_FromLong(10);
PyObject *b = PyLong_FromLong(20);
PUSH(a);
PUSH(b);
Книги для программистов: https://t.me/booksforits
156 Цикл вычисления
Они сохраняются в стеке в порядке добавления, поэтому a попадет на вторую позицию в стеке:
b
|
b |
|
|
|
|
При получении верхнего значения из стека вы получите указатель на b, потому что именно эта переменная сейчас находится на вершине:
PyObject *val = POP(); // Возвращает указатель на b
|
|
b |
b |
a |
a |
|
|
Если вам понадобится получить указатель на верхнее значение в стеке без его извлечения, можно воспользоваться операцией PEEK(v), где v — позиция стека:
PyObject *first = PEEK(0);
0 соответствует вершине стека, а 1 — второй позиции:
a
a |
a |
|
|
Для клонирования значения с вершины стека используется макрос DUP_TOP():
DUP_TOP();
Книги для программистов: https://t.me/booksforits