- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода 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
- •Выводы
- •Благодарности
Структура данных конфигурации среды выполнения 77
Исходные файлы
Ниже перечислены исходные файлы, относящиеся к PyPreConfig.
ФАЙЛ |
НАЗНАЧЕНИЕ |
Python initconfig.c |
Загружает данные конфигурации из системных |
|
параметров среды и объединяет их с флагами |
|
командной строки |
Include cpython initconfig.h |
Определяет структуру данных конфигурации ини- |
|
циализации |
СТРУКТУРА ДАННЫХ КОНФИГУРАЦИИ СРЕДЫ ВЫПОЛНЕНИЯ
Вторая фаза конфигурации — конфигурация среды выполнения. Структура данных конфигурации среды выполнения в PyConfig содержит несколько значений, включая следующие:
zz Флаги среды выполнения для таких режимов, как отладка и оптимизация.
zz Режим выполнения (например, файл сценария, stdin или модуль). zz Расширенные параметры, задаваемые с ключом -X <параметр>. zz Переменные среды для настроек среды выполнения.
Данные конфигурации используются средой выполнения CPython для включения и отключения отдельных возможностей.
Настройка конфигурации среды выполнения в командной строке
Python также включает ряд параметров интерфейса командной строки1. Например, в CPython существует режим подробного вывода, предназначенный прежде всего для отладки CPython разработчиками.
Режим подробного вывода включается флагом -v. Python выводит сообщения на экран при загрузке модулей:
1 https://docs.python.org/3/using/cmdline.html.
Книги для программистов: https://t.me/booksforits
78 Конфигурация и ввод
$ ./python -v -c "print('hello world')"
#installing zipimport hook import zipimport # builtin
#installed zipimport hook
...
Вы увидите сотню и более строк с сообщениями об импортировании веб-пакетов пользователя и всех остальных событий, происходящих в системной среде.
Так как конфигурацию среды выполнения можно задать несколькими способами, у настроек конфигурации существуют разные приоритеты. Порядок приоритетов для режима подробного вывода:
1.Для config->verbose в исходном коде жестко фиксируется значение -1.
2.Переменная среды PYTHONVERBOSE используется для задания значения
config->verbose.
3.Если переменная среды не существует, используется значение по умолчанию -1.
4.В функции config_parse_cmdlin() из файла Python initconfig.c флаг командной строки (если он указан) используется для присваивания значения.
5.Значение копируется в глобальную переменную Py_VerboseFlag при вызове _Py_GetGlobalVariablesAsDict().
Для всех значений PyConfig существует один порядок действий и последовательность приоритетов:
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PyPreCon•g |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PyCon•g |
|
|
|
|
|
|
|
Книги для программистов: https://t.me/booksforits
Конфигурация сборки 79
Просмотр флагов времени выполнения
У интерпретаторов CPython имеется набор флагов времени выполнения. Эти флаги управляют расширенными возможностями по включению/отключению поведения, специфического для CPython. В сеансе Python можно обращаться к флагам времени выполнения (например, режиму подробного вывода и тихому режиму) через кортеж с именем sys.flags.
Все флаги -X доступны в словаре sys._xoptions:
$ ./python -X dev -q
>>>import sys
>>>sys.flags
sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=1, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0)
>>> sys._xoptions {'dev': True}
КОНФИГУРАЦИЯ СБОРКИ
Наряду с конфигурацией среды выполнения вфайле Include cpython initconfig.h также существует конфигурация сборки в файле pyconfig.h корневой папки. Этот файл создается динамически на шаге ./configure процесса сборки в macOS и Linux либо build.bat в Windows.
Чтобы просмотреть конфигурацию сборки, выполните следующую команду:
$ ./python -m sysconfig
Platform: "macosx-10.15-x86_64"
Python version: "3.9"
Current installation scheme: "posix_prefix"
Paths:
data = "/usr/local"
include = "/Users/anthonyshaw/CLionProjects/cpython/Include" platinclude = "/Users/anthonyshaw/CLionProjects/cpython"
...
Книги для программистов: https://t.me/booksforits
80 Конфигурация и ввод
Свойства конфигурации сборки представляют собой значения времени компиляции, используемые для выбора дополнительных модулей, которые компонуются с двоичным файлом. Например, отладчики, инструментальные библиотеки и распределители памяти задаются во время компиляции.
Когда вы выполните три этапа конфигурации, интерпретатор CPython сможет получить входные данные и преобразовать текст в исполняемый код.
СБОРКА МОДУЛЯ ИЗ ВХОДНЫХ ДАННЫХ
Любой код перед выполнением необходимо скомпилировать в модуль из входных данных. Как упоминалось ранее, входные данные могут существовать в разных формах:
zz локальные файлы и пакеты;
zz потоки ввода/вывода (например, stdin или канал в памяти); zz строки.
Входные данные читаются, передаются парсеру, а затем компилятору:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/ |
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Из-за этой гибкости большая часть исходного кода CPython относится к обработке входных данных парсера CPython.
Исходные файлы
Существуют четыре основных файла для взаимодействия с интерфейсом командной строки.
Книги для программистов: https://t.me/booksforits
|
Сборка модуля из входных данных 81 |
|
|
ФАЙЛ |
НАЗНАЧЕНИЕ |
Lib runpy.py |
Модуль стандартной библиотеки для импортирования |
|
и выполнения модулей Python |
Modules main.c |
Функции-обертки для выполнения внешнего кода (на- |
|
пример, из файла, модуля или входного потока) |
Programs python.c |
Точка входа для исполняемого файла python в Windows, |
|
Linux и macOS. Служит оберткой для Modules/main.c |
Python pythonrun.c |
Функции-обертки внутренних API, написанные на C |
|
и предназначенные для обработки ввода из командной |
|
строки |
Чтение файлов и ввод
Как только для CPython настроены среда выполнения и аргументы командной строки, он может загрузить код, который требуется выполнить. Эту задачу решает функция pymain_main() из Modules main.c.
Теперь CPython выполняет заданный код с любыми параметрами, указанными во вновь созданном экземпляре PyConfig.
Получение входных данных из командной строки
CPython может запустить небольшое приложение Python из командной строки с ключом -c. Например, рассмотрим, что произойдет при выполнении print(2 ** 2):
$ ./python -c "print(2 ** 2)"
4
Сначала pymain_run_command() выполняется внутри Modules main.c; при этом команда из -c передается в аргументе с типом C wchar_t*.
Затем pymain_run_command() передает объект Python с набором байтов функции PyRun_SimpleStringFlags() для выполнения.
PyRun_SimpleStringFlags() является частью файла Python pythonrun.c. Его цель — преобразование строки в модуль Python и его последующая передача для выполнения.
Книги для программистов: https://t.me/booksforits
82 Конфигурация и ввод
ПРИМЕЧАНИЕ
Тип wchar_t* часто используется в CPython как низкоуровневый тип для хранения данных в Юникоде, так как размер типа позволяет хранить символы UTF-8.
Припреобразованииwchar_t*встрокуPythonфайлObjects unicodeobject.c
содержит вспомогательную функцию PyUnicode_FromWideChar(),которая возвращает строку в Юникоде.Затем она кодируется в UTF-8 функцией
PyUnicode_AsUTF8String().
Строки в кодировке Юникод в Python подробно рассматриваются в раз деле «Тип строк в Юникоде» главы «Объекты и типы».
Модуль Python должен иметь точку входа __main__ для возможности его автономного выполнения, и неявно создает эту точку входа.
После того как функция PyRun_SimpleStringFlags() создаст модуль и словарь, она вызывает PyRun_StringFlags(). PyRun_SimpleStringFlags() создает фиктивное имя файла, после чего вызывает парсер Python для создания дерева абстрактного синтаксиса1 (AST, abstract syntax tree) из строки и возвращает модуль. Вы узнаете больше об AST в следующей главе.
ПРИМЕЧАНИЕ
Модули Python — структуры данных, используемые для передачи син таксически разобранного кода компилятору. Структура данных C для модулей Python — mod_ty — определяется в Include Python-ast.h.
Получение входных данных из локального модуля
Другой способ выполнения команд Python основан на использовании параметра -m с именем модуля. Типичный пример — команда python -m unittest, которая выполняет модуль unittest из стандартной библиотеки.
1 Иначе — абстрактное синтаксическое дерево. — Примеч. ред.
Книги для программистов: https://t.me/booksforits
Сборка модуля из входных данных 83
Возможность выполнения модулей как скриптов была изначально предложена в PEP 338, а стандарт явного относительного импортирования был определен в PEP 366.
Флаг -m означает, что внутри пакета модуля должно быть выполнено все, что находится внутри точки входа (__main__)1. Он также подразумевает, что вы хотите найти sys.path для модуля с заданным именем.
Именно благодаря механизму поиска в библиотеке импортирования (importlib) вам не приходится запоминать, где в вашей файловой системе хранится модуль unittest.
CPython импортирует модуль стандартной библиотеки runpy и выполняет его с использованием PyObject_Call(). Импортирование осуществляется с использованием API-функции языка C — PyImport_ImportModule(), находящейся в файле Python import.c.
ПРИМЕЧАНИЕ
Если в Python вы захотите получить атрибут для имеющегося объекта, следует вызвать функцию getattr(). В C API этому вызову соответствует функция PyObject_GetAttrString(),находящаяся вфайле Objects object.c.
Если вы захотите использовать вызываемый (callable) объект, поставь те круглые скобки либо вызовите свойство __call__() объекта Python. __call__() реализуется внутри Objects object.c:
>>>my_str = "hello, world"
>>>my_str.upper()
'HELLO, WORLD'
>>> my_str.upper.__call__()
'HELLO, WORLD'
Модуль runpy написан на чистом Python и находится в файле Lib runpy.py.
Выполнение команды python -m <модуль> эквивалентно выполнению команды python -m runpy <модуль>. Модуль runpy был создан для абстрагирования процесса поиска и выполнения модулей в операционной системе.
1 https://realpython.com/python-main-function/.
Книги для программистов: https://t.me/booksforits
84 Конфигурация и ввод
Для запуска целевого модуля runpy выполняет три операции:
1.Для предоставленного имени модуля вызывается __import__().
2.Переменной __name__ (имя модуля) присваивается пространство имен
__main__.
3. Модуль выполняется в пространстве имен __main__.
Модуль runpy также поддерживает выполнение каталогов и ZIP-файлов.
Входные данные из скрипта или стандартного ввода
Если первым аргументом python является имя файла (например, python test.py), то CPython открывает дескриптор файла и передает его функции
PyRun_SimpleFileExFlags() из Python pythonrun.c.
Существуют три разных способа выполнения этой функции в зависимости от пути:
1.Если указан путь до файла .pyc, вызывается run_pyc_file().
2.Если указан путь до файла со скриптом (.py), выполняется PyRun_
FileExFlags().
3.Если указан путь до потока ввода stdin (потому что пользователь выполнил перенаправление <команда> | python), то stdin рассматривается как дескриптор файла и выполняется PyRun_FileExFlags().
Для stdin и базовых файлов со скриптами CPython передает дескриптор файла функции PyRun_FileExFlags(), находящейся в файле Python pythonrun.c file.
Функция PyRun_FileExFlags() делает примерно то же, что и PyRun_ SimpleStringFlags(). CPython загружает дескриптор файла в PyParser_ ASTFromFileObject().
Как и в случае с PyRun_SimpleStringFlags(), после того как PyRun_ FileExFlags() создает модуль Python из файла, он передает его в run_mod() для выполнения.
Книги для программистов: https://t.me/booksforits