Глава 5. Конфигурация и входные данные

Теперь, когда в рассмотрели грамматику Python, самое время исследовать как код переходит в исполняемое состояние.

Существует множество способов, коими код Python может запускаться в CPython. Вот некоторые из наиболее употребимых подходов:

  1. Запустить python -c и некую строку Python

  2. Запустить python -m и название некого модуля

  3. Запустить python <file> с путём к некому файлу, который содержит код Python

  4. Направить в конвейере код Python в некий исполняемый python через stdin, скажем cat <file> | python

  5. Запустить REPL и выполнять команды по одной за раз

  6. Применяя API C пользоваться Python как некой встроенной средой

[Совет]Смотри также

В Python имеется настолько много способов исполнения сценариев, что это может слегка подавлять. Для дополнительных сведений относительно запуска сценариев Python отсылаем вам к материалам Real Python "How to Run Your Python Scripts".

Для исполнения кода Python его интерпретатору требуется обладать тремя элементами:

  1. Исполняемым модулем

  2. Неким состоянием для удержания таких сведений как переменные

  3. Некой конфигурацией, например, какие варианты включены

При наличии этих трёх компонентов имеющийся интерпретатор способен исполнить код и предоставить некий вывод:

 

Рисунок 5-1



[Замечание]Замечание

Аналогично стилю руководства PEP 8 для кода Python, имеется руководство стиля PEP 7 для кода C CPython. Он включает в себя следующие стандарты именования для исходного кода C:

  • Установку префикса Py для общедоступных функций, не для статических функций.

  • Префикс Py_ предназначен для подпрограмм глобальной службы, например, Py_FatalError. Определённые группы подпрограмм (такие как API особого объектного типа) обязаны применять более длинный префикс, такой как PyString_ для строковых функций.

  • Общедоступные функции и переменные должны быть написаны Верблюжьей нотацией, причём с разделением слов символом подчёркивания, например, PyObject_GetAttr(), Py_BuildValue() и PyExc_TypeError().

  • Префикс _Py должен быть зарезервирован для внутренних функций, которые должны быть видимыми имеющемуся загрузчику, допустим, _PyObject_Dump().

  • Макросы обязаны обладать Верблюжьим префиксом и далее применять верхний регистр, причём все слова разделяются символом подчёркивания, например, PyString_AS_STRING и Py_PRINT_RAW.

В отличии от PEP 8, имеется несколько инструментов для проверки совместимости в PEP 7. Вместо этого данная задача выполняется разработчиками ядра в рамках проверки кода. Как и любой управляемый человеком процесс, этот тип проверки не является защищённым от ошибок, а потому вы, скорее всего, обнаружите код, не соответствующий PEP 7..

Единственным встроенным инструментом для автоматизации этого процесса выступает сценарий с названием smelly.py, который вы можете исполнить при помощи цели make smelly в macOS или Linux, либо через командную строку:.


$ ./python Tools/scripts/smelly.py
		

Он распространит ошибку вверх для всех символов, которые пребывают в libpython (стандартной библиотеке CPython), но не начинаются с Py или _Py.

Состояние конфигурации

Прежде чем исполнять любой код Python, имеющийся CPython времени исполнения вначале устанавливает значение конфигурации времени исполнения и разные параметры предоставляемые пользователем.

Как это предопределено PEP 587, значение конфигурации времени исполнения располагается в трёх местах:

  • PyPreConfig, используемом для конфигурации предварительной инициализации

  • PyConfig, применяемом для конфигурации времени выполнения

  • В скомпилированной конфигурации самого интерпретатора CPython

Обе структуры данных, PyPreConfig и PyConfig определены в nclude/cpython/initconfig.h.

  Предварительная инициализация конфигурации

Конфигурация предварительной инициализации отделена от конфигурации времени выполнения, поскольку её свойства относятся к установленной операционной системе или среде пользователя.

PyPreConfig обладает тремя первичными функциями:

  1. Монтаже распределителя памяти Python

  2. Настройке значения локализации LC_CTYPE на значение системной или предпочитаемой пользователем локализации

  3. Монтаже режима UTF-8 (PEP 540)

Сам тип PyPreConfig содержит следующие поля, причём все с типом int:

  • allocator: Выбирает распределитель памяти, например, PYMEM_ALLOCATOR_MALLOC. Для получения дополнительных сведений относительно распределителя памяти запустите ./configure --help.

  • configure_locale: Устанавливает локализацию LC_CTYPE в значение предпочитаемой пользователем. Когда равен 0, тогда устанавливает coerce_c_locale и coerce_c_locale_warn равным 0.

  • coerce_c_locale: Если равен 2, то приводится к локализации C. Если равен 1, тогда считывается значение локализации LC_CTYPE для принятия решения относительно тог должна ли они приводиться.

  • coerce_c_locale_warn: В случае когда не равно нулю, выдаётся предупреждение если приводится локализация C/

  • dev_mode: Включает режим разработки.

  • isolated: Разрешает режим изоляции. sys.path не содержит ни значения каталога сценария, ни значения каталога пакетов площадки пользователя.

  • legacy_windows_fs_encoding: (Только для Windows) при неравенстве нулю, отключает режим UTF-8 и устанавливает файловую систему Python на кодирование в mbcs.

  • parse_argv: При неравенстве нулю используются параметры командной строки.

  • use_environment: Когда больше нуля, применяются переменные среды.

  • utf8_mode: При неравенстве нулю разрешается режим UTF-8.

  Относящиеся к делу исходные файлы

Ниже приводятся относящиеся к PyPreConfig исходные файлы:

Таблица 5-1. Относящиеся к PyPreConfig исходные файлы
Файл Назначение

Python/initconfig.c

Загружает значения конфигурации из среды своей системы и сливает её со всеми флагами командной строки

Include/cpython/initconfig.h

Определяет структуру данных инициализирующей конфигурации

  Структура данных конфигурации времени исполнения

Второй этап настроек состоит в конфигурации времени выполнения. Структура данных настроек времени выполнения в PyConfig содержит некоторые значения, включая следующие:

  • Флаги времени выполнения для таких режимов как отладочный и оптимизирующий

  • Значение режима исполнения, например, в качестве файла сценария, stdin или модуля

  • Расширенные опции, предписываемые через -X <option>

  • Переменные среды для настроек времени выполнения

Эти данные конфигурации применяются CPython времени выполнения для включения и отключения функциональных возможностей.

  Монтаж конфигурации времени исполнения при помощи командной строки

Python также поставляется с некоторыми вариантами взаимодействия командной строки. К примеру, CPython обладает режимом с названием verbose mode (подробный режим). Он в первую очередь предназначен разработчикам для отладки CPython.

Вы можете разрешить подробный режим при помощи флага -v и Python будет печатать на экран сообщения при загрузке модулей:


$ ./python -v -c "print('hello world')"

# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
...
		

Вы обнаружите сотни строк или даже больше со всеми вашими импортами пакетов вашей пользовательской площадки и чем- то ещё в вашей системной среде.

Поскольку конфигурация времени выполнения может настраиваться различными способами, настройки конфигурации обладают уровнями следования. Вот порядок следования подробного режима:

  1. Установленные по умолчанию значения для config->verbose жёстко закодированы в -1 в имеющемся исходном коде

  2. Для настройки значения config->verbose применяется переменная среды PYTHONVERBOSE

  3. Если эта переменная не существует, тогда остаётся назначенным установленное по умолчанию значение значение -1

  4. В config_parse_cmdline() внутри Python/initconfig.c, если он предоставлен, применяется для установки соответствующего значения флаг командной строки.

  5. Это значение копируется в глобальную переменную, Py_VerboseFlag через _Py_GetGlobalVariablesAsDict().

Все значения PyConfig следуют теми же самыми последовательностями и порядком следования:

 

Рисунок 5-2



  Просмотр флагов времени исполнения

Интерпретаторы 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 в процессе сборки для macS и 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"
...
		

Встроенные свойства конфигурации являются значениями времени компиляции, применяемыми для выбора дополнительных модулей, для их связывания с создаваемым исполняемым файлом. Например, отладчиков, инструментальных библиотек, а также распределителей памяти, которые все монтируются во время компиляции.

Со всеми этими тремя этапами конфигурации, наш CPython теперь может получать входные данные и перерабатывать получаемый текст в исполняемый код.

Сборка модуля из входных данных

Прежде чем код может быть исполнен, он должен быть скомпилирован в некий модуль из получаемых на вход данных. Как уже обсуждалось ранее, входные данные могут различаться по своему типу:

  • Локальные файлы и пакеты

  • Потоки ввода/ вывода, например, stdin или конвейер в памяти

  • Строки

Входные данные считываются, передаются своему синтаксическому анализатору, а затем передаются в сам компилятор:

 

Рисунок 5-3



Благодаря такой гибкости большая часть исходного кода CPython посвящена обработке входных данных в своём синтаксическом анализаторе CPython.

  Относящиеся к делу файлы исходного кода

Имеются четыре основных файла, с которыми имеет дело интерфейс командной строки:

Таблица 5-2. Относящиеся к взаимодействию с командной строкой файлы
Файл Назначение

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
		

Прежде всего, внутри Modules/main.c выполняется pymain_run_command(), беря ту команду, которая передаётся в -c в качестве аргумента в C с типом wchar_t*.

[Замечание]Замечание

Тип wchar_t* часто используется для типа хранения нижнего уровня под данные Unicode по всему CPython, так как размер этого типа способен хранить символы UTF-8.

При преобразовании значения wchar_t* в некую строку Python, файл Objects unicodeobject.c обладает вспомогательной функцией, PyUnicode_FromWideChar(), которая и возвращает строку Unicode. Её кодирование в UTF-8 далее производится посредством PyUnicode_AsUTF8String().

Строки Unicode Python подробно обсуждаются в разделе Тип строки Unicode Главы 11, Объекты и типы.

После того как это осуществлено, pymain_run_command() передаёт Python объект байт в PyRun_SimpleStringFlags() для исполнения.

PyRun_SimpleStringFlags() является частью Python/pythonrun.c. Его цель состоит в повороте строки в модуль Python с последующей отправкой её на выполнение.

Модулю Python требуется некая точка входа, __main__, для его обособленного исполнения и PyRun_SimpleStringFlags() создаёт её в неявном виде.

После того как PyRun_SimpleStringFlags() создал модуль и некий словарь, он вызывает PyRun_StringFlags(). PyRun_StringFlags() создаёт поддельное имя файла и затем вызывает синтаксический анализатор Python для создания дерева абстрактного синтаксиса (AST, abstract syntax tree) из этой строки и возвращает некий модуль. В нашей следующей главе вы получите дополнительные сведения относительно AST.

[Замечание]Замечание

Модули Python это определённые структуры данных, применяемые для удобства синтаксического анализа кода в самом компиляторе. Структурой C для модуля Python выступает mod_ty и она определена в Include/Python-ast.h.

  Просмотр флагов времени исполнения

Другой способ выполнения команд Python состоит в применении параметра -m с названием некого модуля. Типичным примером является python -m unittest, который запускает модуль unittest в стандартной библиотеке.

Такая возможность исполнения модулей в качестве сценариев изначально предлагалась в PEP 338. Окончательный стандарт для относительных импортов в явном виде был определён в PEP 366.

Флаг -m подразумевает, что внутри соответствующего пакета модуля вы желаете выполнить что- то внутри соответствующей точки входа (__main__). Это также подразумевает, что вы хотите отыскать sys.path для модуля с этим названием.

Такой механизм поиска в соответствующей импортируемой библиотеке (importlib) это то, почему вам нет нужды помнить где ваш модуль unittest хранится в вашей файловой системе.

CPython импортирует стандартный библиотечный модуль, runpy и выполняет его при помощи и PyObject_Call(). Такой импорт выполняется при помощи API функции Import_ImportModule() C, обнаруживаемой внутри файла Python/import.c.

[Замечание]Замечание

В Python, если у вас имеется некий объект и вы желаете получить атрибут, вы можете вызвать getattr(). В соответствующем API C этим вызовом является PyObject_GetAttrString(), который обнаруживается в Objects/object.c.

Если вы желаете вызвать вызывавшего, тогда вы можете заключить его в круглые скобки, или же можете вызывать свойство __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 <module> эквивалентно python -m runpy <module>. Модуль runpy был создан для абстрагирования самого процесса от местоположения и выполнения модулей в некой операционной системе.

Модуль 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, тогда она запускает Главе 2,

  3. Если файловым путём является stdin по той причине, что пользователь запустил <command> | python, тогда он трактует stdin как некий дескриптор файла и запускает PyRun_FileExFlags()

Для stdin и базовых файлов сценариев CPython будет передавать файловый дескриптор в PyRun_FileExFlags(), расположенный в файле Python/pythonrun.c.

Основное назначение PyRun_FileExFlags() аналогично PyRun_SimpleStringFlags(). CPython будет загружать полученный файловый дескриптор в PyParser_ASTFromFileObject().

Идентично PyRun_SimpleStringFlags(), после того как PyRun_FileExFlags() создал некий модуль Python из соответствующего файла, он отправляет этот модуль в run_mod() на исполнение.

  Просмотр флагов времени исполнения

Когда некий пользователь запускает python с каким- то путём к файлу .pyc, тогда вместо загрузки такого файла как обычного текстового файла и его синтаксического анализа, CPython будет предполагать что такой файл .pyc содержит некий кодовый объект, записанный на диск.

В PyRun_SimpleFileExFlags() имеется некое выражение для того чтобы соответствующий пользователь предоставлял некий путь файла для файла .pyc.

run_pyc_file() внутри Python pythonrun.c выстраивает (маршализует) соответствующий кодовый объект из своего файла .pyc применяя некий файловый дескриптор.

Такая структура кодового объекта на диске является тем способом, коим компилятор CPython кэширует скомпилированный код с тем, чтобы не выполнять его синтаксический анализ всякий раз, когда вызывается данный сценарий.

[Замечание]Замечание

Marshaling (выстраивание) это термин для копирования необходимого содержимого какого- то файла в оперативную память и преобразование его в специфическую структуру данных.

После того как соответствующий кодовый объект был выстроен в оперативной памяти, он отправляется в run_eval_code_obj(), который вызывает Python/ceval.c для выполнения этого кода.

Выводы

В этой главе вы раскрыли как загружается большое число вариантов конфигурации Python и как код предоставляется на вход его интерпретатору.

Гибкость Python с входными данными превращает его в великолепный инструмент для некого диапазона приложений, таких как:

  • Утилиты командной строки

  • Сетевые приложения с длительным временем работы, например, веб серверы

  • Коротких, компактных сценариев

Способность Python устанавливать свойства конфигурации многими способами вносит некую сложность. Например, когда вы тестируете некое приложение Python в Python 3.8 и оно выполняется корректно, но если затем оно отказывает в некой другой среде, тогда вам потребуется разобраться какие настройки были другими в этой среде.

Это означает, что вам потребуется проинспектировать переменные среды, флаги времени выполнения и даже свойства системной конфигурации.

Соответствующие свойства времени компиляции, обнаруживаемые в системной конфигурации могут разниться между дистрибутивами Python. Например, Python 3.8, выгруженный с Python.org под macOS имеет другие значения по умолчанию, нежели тот дистрибутив Python 3.8, который обнаруживается в Homebrew или в дистрибутиве Anaconda.

Все эти методы входных данных выдают некий модуль Python. В нашей следующей главе вы увидите как из входных данных создаются модули.