Документация Python неофициальный перевод
Содержание страницы

Определение модулей расширенияDefining extension modules

Расширение на C для CPython – это разделяемая библиотека (например, файл .so в Linux, .pyd DLL в Windows), которая загружается в процесс Python (например, скомпилирована с совместимыми настройками компилятора) и экспортирует функцию экспортного хука (или старую функцию инициализации).

Чтобы библиотека импортировалась по умолчанию (то есть с помощью importlib.machinery.ExtensionFileLoader), она должна быть доступна в sys.path, и её имя должно соответствовать имени модуля с расширением, указанным в importlib.machinery.EXTENSION_SUFFIXES.

Примечание

Сборку, упаковку и распространение модулей расширения лучше выполнять с помощью сторонних инструментов, и это выходит за рамки данного документа. Подходящим инструментом является Setuptools, документация которого находится по адресу https://setuptools.pypa.io/en/latest/setuptools.html.

Экспортный хук расширенияExtension export hook

Добавлено в версии 3.15: Поддержка PyModExport_<name> экспортного хука была добавлена в Python 3.15. Старый способ определения модулей по-прежнему доступен: обратитесь к разделу функции PyInit или к более ранним версиям этой документации, если вы планируете поддерживать более старые версии Python.

Экспортный хук должен быть экспортируемой функцией со следующей сигнатурой:

PySlot *PyModExport_modulename(void)

Для модулей, имена которых содержат только ASCII, экспортный хук должен называться PyModExport_<name>, где <name> заменяется на имя модуля.

Для модулей с не-ASCII именами экспортный хук должен называться PyModExportU_<name> (обратите внимание на U), где <name> кодируется с помощью кодировки punycode Python, а дефисы заменяются на символы подчеркивания. В Python:

def hook_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyModExport' + suffix

Экспортный хук возвращает массив из PySlot записей, завершающийся записью с идентификатором слота 0. Эти слоты описывают, как модуль должен быть создан и инициализирован.

Этот массив должен оставаться действительным и постоянным до завершения работы интерпретатора. Обычно он должен использовать хранилище static. Для любого динамического поведения предпочтительнее использовать слоты Py_mod_create и Py_mod_exec.

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

Рекомендуется определять функцию экспортного хука с помощью вспомогательного макроса:

PyMODEXPORT_FUNC
Часть Stable ABI начиная с версии 3.15.

Объявляет экспортный хук модуля расширения. Этот макрос:

  • определяет тип возвращаемого значения PySlot*,

  • добавляет любые специальные объявления компоновки, требуемые платформой, и

  • для C++ объявляет функцию как extern "C".

Например, модуль с именем spam был бы определен так:

PyABIInfo_VAR(abi_info);

static PySlot spam_slots[] = {
    PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
    PySlot_STATIC_DATA(Py_mod_name, "spam"),
    PySlot_FUNC(Py_mod_init, spam_init_function),
    ...
    PySlot_END
};

PyMODEXPORT_FUNC
PyModExport_spam(void)
{
    return spam_slots;
}

Экспортный хук обычно – единственный не-static элемент, определённый в C-исходном коде модуля.

Хук должен быть коротким. Если он делает больше, чем return статический массив, применяются несколько предостережений:

  • Если нужно использовать любой Python C API, рекомендуется сначала вызвать PyABIInfo_Check(), чтобы возбудить исключение, а не упасть, в распространённых случаях несовместимости ABI.

  • Код в экспортном хуке никогда не должен полагаться на GIL: сборки со свободными потоками Python могут проверять слот Py_mod_gil (или его отсутствие) только после возврата хука,

  • Аналогично, хук может быть вызван в любом суб-интерпретаторе, так как слот Py_mod_multiple_interpreters (или его отсутствие) проверяется только после возврата хука.

Например:

PyMODEXPORT_FUNC
PyModExport_modulename(void)
{
   if (PyABIInfo_Check(&abi_info, "modulename") < 0) {
      /* Несовместимость ABI. Проверять возникшее исключение небезопасно. */
      return NULL;
   }

   /* используйте Python API (как можно реже); не полагайтесь на GIL. */

   return modulename_slots;
}

Примечание

It is possible to export multiple modules from a single shared library by defining multiple export hooks. However, importing them requires a custom importer or suitably named copies/links of the extension file, because Python’s import machinery only finds the function corresponding to the filename. See the Multiple modules in one library section in PEP 489 for details.

Многофазная инициализацияMulti-phase initialization

Процесс создания модуля расширения состоит из нескольких фаз:

  • Python находит и вызывает экспортный хук, чтобы получить информацию о том, как создать модуль.

  • Перед выполнением любого существенного кода Python может определить, какие возможности поддерживает модуль, и может настроить окружение или отказаться загружать несовместимое расширение. Слоты, такие как Py_mod_abi, Py_mod_gil и Py_mod_multiple_interpreters, влияют на этот шаг.

  • По умолчанию Python сам создаёт объект модуля – то есть делает эквивалент вызова __new__() при создании объекта. Этот шаг можно переопределить с помощью слота Py_mod_create.

  • Python устанавливает начальные атрибуты модуля, такие как __package__ и __loader__, и вставляет объект модуля в sys.modules.

  • После этого объект модуля инициализируется специфичным для расширения способом – эквивалентом __init__() при создании объекта или выполнения кода верхнего уровня в модуле на Python. Поведение задаётся с помощью слота Py_mod_exec.

This is called multi-phase initialization to distinguish it from the legacy (but still supported) single-phase initialization, where an initialization function returns a fully constructed module.

Changed in version 3.5: Added support for multi-phase initialization (PEP 489).

Несколько экземпляров модуляMultiple module instances

По умолчанию модули расширения не являются синглтонами. Например, если запись sys.modules удалена и модуль импортируется заново, создаётся новый объект модуля и, как правило, заполняется новыми объектами методов и типов. Старый модуль подлежит обычной сборке мусора. Это повторяет поведение чисто Python-модулей.

Additional module instances may be created in sub-interpreters or after Python runtime reinitialization (Py_Finalize() and Py_Initialize()). In these cases, sharing Python objects between module instances would likely cause crashes or undefined behavior.

To avoid such issues, each instance of an extension module should be isolated: changes to one instance should not implicitly affect the others, and all state owned by the module, including references to Python objects, should be specific to a particular module instance. See Isolating Extension Modules for more details and a practical guide.

A simpler way to avoid these issues is raising an error on repeated initialization.

All modules are expected to support sub-interpreters, or otherwise explicitly signal a lack of support. This is usually achieved by isolation or blocking repeated initialization, as above. A module may also be limited to the main interpreter using the Py_mod_multiple_interpreters slot.

PyInit функцияPyInit function

Soft deprecated since version 3.15: This functionality will not get new features, but there are no plans to remove it.

Вместо PyModExport_modulename() модуль расширения может определить функцию инициализации старого стиля со следующей сигнатурой:

PyObject *PyInit_modulename(void)

Its name should be PyInit_<name>, with <name> replaced by the name of the module. For non-ASCII module names, use PyInitU_<name> instead, with <name> encoded in the same way as for the export hook (that is, using Punycode with underscores).

Если модуль экспортирует и PyInit_<name>, и PyModExport_<name>, функция PyInit_<name> игнорируется.

Как и в случае с PyMODEXPORT_FUNC, рекомендуется определять функцию инициализации с помощью вспомогательного макроса:

PyMODINIT_FUNC

Объявляет функцию инициализации модуля расширения. Этот макрос:

  • задаёт возвращаемый тип PyObject*,

  • добавляет любые специальные объявления компоновки, требуемые платформой, и

  • для C++ объявляет функцию как extern "C".

Обычно функция инициализации (PyInit_modulename) возвращает экземпляр PyModuleDef с не-NULL m_slots. Это позволяет Python использовать многофазную инициализацию.

Перед возвратом экземпляр PyModuleDef должен быть инициализирован с помощью следующей функции:

PyObject *PyModuleDef_Init(PyModuleDef *def)
Часть Stable ABI начиная с версии 3.5.

Гарантирует, что определение модуля является правильно инициализированным объектом Python, который корректно сообщает свой тип и счетчик ссылок.

Возвращает def, приведённый к PyObject*, или NULL в случае ошибки.

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

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

Добавлено в версии 3.5.

Например, модуль с именем spam был бы определён так:

static struct PyModuleDef spam_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    ...
};

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModuleDef_Init(&spam_module);
}

Устаревшая однофазная инициализацияLegacy single-phase initialization

Мягко устарело начиная с версии 3.15: Однофазная инициализация – это устаревший механизм инициализации модулей расширения с известными недостатками и ошибками проектирования. Авторам модулей расширения рекомендуется вместо этого использовать многофазную инициализацию.

Однако планов по удалению поддержки этого механизма нет.

При однофазной инициализации функция инициализации старого образца (PyInit_modulename) должна создать, заполнить и вернуть объект модуля. Обычно это делается с помощью PyModule_Create() и таких функций, как PyModule_AddObjectRef().

Однофазная инициализация отличается от стандартной следующим образом:

  • Однофазные модули являются, или, точнее, содержат «одиночки» (синглтоны).

    При первой инициализации модуля Python сохраняет содержимое __dict__ модуля (то есть, как правило, функции и типы модуля).

    При последующих импортах Python не вызывает функцию инициализации снова. Вместо этого он создаёт новый объект модуля с новым __dict__ и копирует в него сохранённое содержимое. Например, для однофазного модуля _testsinglephase [1], который определяет функцию sum и класс исключения error:

    >>> import sys
    >>> import _testsinglephase as one
    >>> del sys.modules['_testsinglephase']
    >>> import _testsinglephase as two
    >>> one is two
    False
    >>> one.__dict__ is two.__dict__
    False
    >>> one.sum is two.sum
    True
    >>> one.error is two.error
    True
    

    Точное поведение следует считать деталью реализации CPython.

  • Чтобы обойти тот факт, что PyInit_modulename не принимает аргумент spec, некоторое состояние механизма импорта сохраняется и применяется к первому подходящему модулю, созданному во время вызова PyInit_modulename. В частности, при импорте подмодуля этот механизм добавляет имя родительского пакета к имени модуля.

    Функция PyInit_modulename однофазного модуля должна создать «свой» объект модуля как можно раньше, до того как могут быть созданы любые другие объекты модулей.

  • Не-ASCII имена модулей (PyInitU_modulename) не поддерживаются.

  • Однофазные модули поддерживают функции поиска модулей, такие как PyState_FindModule().

  • PyModuleDef.m_slots модуля должен быть NULL.