- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
Цикл вычисления
Мы увидели, как Python-код превращается в дерево абстрактного синтаксиса и компилируется в объекты кода. Эти объекты кода содержат списки отдельных операций в форме байт-кода.
Но чтобы объекты кода заработали, им кое-чего не хватает. Им необходимы входные данные. В Python входные данные принимают вид локальных и глобальных переменных.
В этой главе вы познакомитесь с концепцией стека значений, в котором переменные создаются, изменяются и используются операциями байт-кода скомпилированных объектов кода.
Код в CPython выполняется в центральном цикле, который называется циклом вычисления. Интерпретатор CPython вычисляет и выполняет объект кода, полученный из файла .pyc или от компилятора:
|
AST |
|
CFG |
|
- |
|
|
|
|
||||
|
|
|
|
|
|
|
В цикле вычисления берется каждая инструкция байт-кода и выполняется в кадровой системе стека.
ПРИМЕЧАНИЕ
Кадры стека — тип данных, используемый во многих средах выполне ния, не только в Python. Стековые кадры позволяют вызывать функции и возвращать переменные из вызовов. Также они содержат аргументы, локальные переменные и другую информацию с состоянием.
Книги для программистов: https://t.me/booksforits
Важные термины 141
Кадр стека существует для каждого вызова функции, причем кадры объединяются в стек последовательно. Содержимое стека CPython вы водится каждый раз при выдаче необработанного исключения:
Traceback (most recent call last):
File "example_stack.py", line 8, in <module> <--- Кадр function1()
File "example_stack.py", line 5, in function1 <--- Кадр function2()
File "example_stack.py", line 2, in function2 <--- Кадр raise RuntimeError
RuntimeError
ИСХОДНЫЕ ФАЙЛЫ
Ниже перечислены исходные файлы, относящиеся к циклу вычисления.
ФАЙЛ |
НАЗНАЧЕНИЕ |
Python ceval.c |
Базовая реализация цикла вычисления |
Python ceval-gil.h |
Определение GIL1 и управляющий алгоритм |
ВАЖНЫЕ ТЕРМИНЫ
Ниже перечислены некоторые ключевые термины, встречающиеся в этой главе:
zz Цикл вычисления берет объект кода и преобразует его в серию объектов кадров.
zz Интерпретатор имеет хотя бы один поток (thread). zz Каждый поток характеризуется состоянием потока. zz Объекты кадров выполняются в стеке кадров.
zz Для ссылок на переменные используется стек значений.
1 Глобальная блокировка интерпретатора. — Примеч. ред.
Книги для программистов: https://t.me/booksforits
142 Цикл вычисления
ПОСТРОЕНИЕ СОСТОЯНИЯ ПОТОКА
Прежде чем кадр может быть выполнен, он должен связаться с потоком. В CPython сразу несколько потоков могут выполняться одновременно внутри одного интерпретатора. Состояние интерпретатора включает связанный список таких потоков.
CPython всегда имеет хотя бы один поток, и каждый поток обладает собственным состоянием.
СМ. ТАКЖЕ
Потоки более подробно рассматриваются в главе«Параллелизм и кон курентность».
Тип состояния потока
Тип состояния потока PyThreadState содержит свыше тридцати свойств, среди которых:
zz Уникальный идентификатор.
zz Связанный список с состояниями других потоков.
zz Состояние интерпретатора, которым был порожден поток. zz Кадр, выполняемый в настоящее время.
zz Текущая глубина рекурсии.
zz Необязательные функции трассировки.
zz Исключение, обрабатываемое в настоящий момент.
zz Асинхронное исключение, обрабатываемое в настоящий момент.
zz Стек поднятых исключений, если их несколько (например, внутри блока except).
zz Счетчик GIL.
zz Счетчики асинхронного генератора.
Книги для программистов: https://t.me/booksforits
Построение объектов кадров 143
Исходные файлы
Исходники, относящиеся к состоянию потоков, распределены на несколько файлов:
ФАЙЛ |
НАЗНАЧЕНИЕ |
Python thread.c |
Реализация API потоков |
Include threadstate.h |
Некоторые API состояния потока и определения типов |
Include pystate.h |
API состояния интерпретатора и определения типов |
Include pythread.h |
API потоков |
Include cpython |
Некоторые API потоков и состояния интерпретатора |
pystate.h |
|
ПОСТРОЕНИЕ ОБЪЕКТОВ КАДРОВ
Скомпилированные объекты кода вставляются в объекты кадров. Объекты кадров являются типом Python, поэтому к ним можно обращаться как из C, так и из Python.
Объекты кадров также содержат другие данные среды выполнения, необходимые для исполнения инструкций в объектах кода. Эти данные включают локальные переменные, глобальные переменные и встроенные модули.
Тип объекта кадра
Тип объекта кадра — PyObject со следующими дополнительными свойствами.
ПОЛЕ |
ТИП |
НАЗНАЧЕНИЕ |
f_back |
PyFrameObject * |
Указатель на предыдущий кадр в стеке или |
|
|
NULL для первого кадра |
f_blockstack |
PyTryBlock[] |
Последовательность блоков for, try и loop |
f_builtins |
PyObject * (dict) |
Таблица символических имен для модуля |
|
|
builtin |
Книги для программистов: https://t.me/booksforits
144 Цикл вычисления
ПОЛЕ |
ТИП |
НАЗНАЧЕНИЕ |
f_code |
PyCodeObject * |
Объект кода, который нужно выполнить |
f_executing |
char |
Флаг, указывающий, что кадр еще выполня- |
|
|
ется |
f_gen |
PyObject * |
Ссылка на генератор или NULL |
f_globals |
PyObject * (dict) |
Таблица глобальных символических имен |
|
|
(PyDictObject) |
f_iblock |
int |
Индекс кадра в f_blockstack |
f_lasti |
int |
Последняя инструкция |
f_lineno |
int |
Номер текущей строки |
f_locals |
PyObject * |
Таблица локальных символических имен |
|
|
(произвольное отображение) |
f_localsplus |
PyObject *[] |
Объединение locals и stack |
f_stacktop |
PyObject ** |
Следующий свободный слот в f_valuestack |
f_trace |
PyObject * |
Указатель на пользовательскую функцию |
|
|
трассировки (см.«Трассировка выполнения |
|
|
кадров») |
f_trace_lines |
char |
Переключение пользовательской функции |
|
|
трассировки на трассировку на уровне |
|
|
строки |
f_trace_opcodes |
char |
Переключение пользовательской функции |
|
|
трассировки на трассировку на уровне кода |
|
|
операции |
f_valuestack |
PyObject ** |
Указатель на последнее локальное значение |
Исходные файлы
Исходные файлы, относящиеся к объектам кадров:
ФАЙЛ НАЗНАЧЕНИЕ
Objects frameobject.c Реализация объекта кадра и Python API
Include frameobject.h |
API объекта кадра и определение типа |
Книги для программистов: https://t.me/booksforits
Построение объектов кадров 145
API инициализации объекта кадра
API инициализации объекта кадра, PyEval_EvalCode(), является точкой входа для вычисления объекта кода и оберткой для внутренней функции
_PyEval\_EvalCode().
ПРИМЕЧАНИЕ
_PyEval_EvalCode() — сложная функция,которая определяет многие осо бенности поведения как объектов кадров, так и цикла интерпретатора. Очень важно понимать эту функцию,так как она демонстрирует некото рые принципы архитектуры интерпретатора CPython.
В этом разделе мы последовательно разберем логику _PyEval\_EvalCode().
_PyEval_EvalCode() определяет группу аргументов:
zz tstate: аргумент PyThreadState*, указывающий на состояние потока, в котором будет вычисляться этот код;
zz _co: аргумент PyCodeObject* с кодом, который должен быть помещен в объект кадра;
zz globals: PyObject* (dict) с именами переменных (ключи) и их значениями;
zz locals: PyObject* (dict) с именами переменных (ключи) и их значениями.
ПРИМЕЧАНИЕ
В Python локальные и глобальные переменные хранятся в виде словаря.
Для обращения к словарю можно воспользоваться встроенными функ циями locals() и globals():
>>>a = 1
>>>print(locals()["a"])
1
Другие аргументы не являются обязательными и не используются в базовом API:
Книги для программистов: https://t.me/booksforits
146 Цикл вычисления
zz argcount: количество позиционных аргументов;
zz args: PyObject* (tuple) со значениями позиционных аргументов;
zz closure: кортеж со строками, которые объединяются в поле co_freevars объекта кода;
zz defcount: длина списка значений по умолчанию для позиционных аргументов;
zz defs: список значений по умолчанию для позиционных аргументов; zz kwargs: список значений именованных аргументов;
zz kwcount: количество именованных аргументов;
zz kwdefs: словарь со значениями по умолчанию для именованных аргументов;
zz kwnames: список имен именованных аргументов; zz name: имя команды вычисления (строка);
zz qualname: уточненное имя команды вычисления (строка).
Вызов _PyFrame_New_NoTrack() создает новый кадр. Этот API также доступен из C API с использованием PyFrame_New(). _PyFrame_New_NoTrack() создает новый объект PyFrameObject по следующей схеме:
1.Свойству f_back присваивается последний кадр состояния потока.
2.С помощью установки значения свойства f_builtins и загрузки модуля builtins вызовом PyModule_GetDict() загружаются текущие встроенные функции.
3.Свойству f_code задается вычисляемый объект кода.
4.Свойству f_valuestack задается пустой стек значений.
5.Указателю f_stacktop присваивается f_valuestack.
6.Свойству f_globals присваивается аргумент globals.
7.Свойству f_locals присваивается новый словарь.
8.Свойству co_firstlineno присваивается значение f_lineno, чтобы трассировка содержала номера строк.
9.Всем остальным свойствам значение задается по умолчанию.
Теперь, когда создан новый экземпляр PyFrameObject, можно построить аргументы объекта кадра:
Книги для программистов: https://t.me/booksforits
Построение объектов кадров 147
|
|
|
|
|
||||
|
|
|
|
|
|
|||
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
• • |
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Преобразование именованных параметров в словарь
Определения функций могут содержать универсальное обозначение **kwargs для именованных аргументов:
def example(arg, arg2=None, **kwargs):
print(kwargs["x"], kwargs["y"]) # Преобразуется в ключ словаря
example(1, x=2, y=3) |
# 2 3 |
В этом скрипте создается новый словарь, а непреобразованные аргументы копируются. Затем в локальной области видимости кадра создается переменная с именем kwargs.
Преобразование позиционных аргументов в переменные
Каждый из позиционных аргументов (если они предоставлены) задается как локальная переменная. В Python аргументы функций уже являются локальными переменными в теле функции. Когда позиционный аргумент определяется со значением, он становится доступным в области видимости функции:
def example(arg1, arg2): print(arg1, arg2)
example(1, 2) # 1 2
Книги для программистов: https://t.me/booksforits
148 Цикл вычисления
Счетчик ссылок для этих переменных увеличивается, поэтому сборщик мусора не удаляет их до вычисления кадра, например при завершении функции и возврате результата.
Упаковка позиционных аргументов в *args
Как и в случае с **kwargs, аргумент функции с символом * может использоваться для перехвата всех оставшихся позиционных аргументов. Создается локальная переменная типа кортеж с именем *args:
def example(arg, *args): print(arg, args[0], args[1])
example(1, 2, 3) # 1 2 3
Загрузка именованных аргументов
Если функция вызывается с именованными аргументами и значениями, то все оставшиеся именованные аргументы, переданные при вызове, которые не преобразуются ни в именованные, ни в позиционные аргументы, попадают в словарь.
Например, аргумент e не является ни позиционным, ни именованным, поэтому он добавляется в **remaining:
>>>def my_function(a, b, c=None, d=None, **remaining): print(a, b, c, d, remaining)
>>>my_function(a=1, b=2, c=3, d=4, e=5)
(1, 2, 3, 4, {"e": 5})
Преобразование значений из словаря именованных аргументов происходит после распаковки всех остальных аргументов. Для исключительно позиционных аргументов PEP 570 цикл именованных аргументов начинается с co_posonlyargcount. Если символ / использовался в третьем аргументе, то значение co_posonlyargcount будет равно 2.
PyDict_SetItem() вызывается для каждого оставшегося аргумента, чтобы он был добавлен в словарь locals. При выполнении каждый из именованных аргументов становится локальной переменной с соответствующей областью видимости.
Книги для программистов: https://t.me/booksforits
Построение объектов кадров 149
ПРИМЕЧАНИЕ
Исключительно позиционные аргументы (position-only arguments) появи лись в Python 3.8.Представленные в PEP 570,исключительно позицион ные аргументы не позволяют пользователям вашего API использовать позиционные аргументы по синтаксису именованных.
Например, следующая простая функция преобразует температуру по Фаренгейту в температуру по шкале Цельсия. Обратите внимание на косую черту (/)— это специальный аргумент,отделяющий исключительно позиционные аргументы от других аргументов:
def to_celsius(fahrenheit, /, options=None): return (fahrenheit-32)*5/9
Всеаргументыслеваот/должныпередаватьсятолькокакпозиционные.Ар гументысправамогутпередаватьсякакпозиционныеиликакименованные:
>>> to_celsius(110)
Если в вызов функции, объявленной с исключительно позиционным аргументом, передать именованный аргумент, возникнет ошибка типа
TypeError:
>>> to_celsius(fahrenheit=110) Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: to_celsius() got some positional-only arguments passed as keyword arguments: 'fahrenheit'
Если именованный аргумент определяется со значением, то оно будет доступно в этой области видимости:
def example(arg1, arg2, example_kwarg=None):
print(example_kwarg) # example_kwarg уже является локальной переменной.
Добавление отсутствующих позиционных аргументов
Любые дополнительные позиционные аргументы, переданные при вызове функции, добавляются в кортеж *args. Если кортеж не существует, выдается исключение.
Книги для программистов: https://t.me/booksforits