Определение модулей расширения
Расширение C для CPython — это разделяемая библиотека (например, файл .so в Linux или DLL .pyd в Windows), которую можно загрузить в процесс Python (например, она скомпилирована с совместимыми настройками компилятора) и которая экспортирует функцию инициализации.
Чтобы модуль можно было импортировать по умолчанию (то есть с помощью importlib.machinery.ExtensionFileLoader), разделяемая библиотека должна быть доступна в sys.path, а её имя должно состоять из имени модуля и расширения из списка importlib.machinery.EXTENSION_SUFFIXES.
Примечание
Для сборки, упаковки и распространения модулей расширения лучше всего использовать сторонние инструменты; эти задачи не входят в область рассмотрения данного документа. Подходящий инструмент — Setuptools; документацию к нему можно найти по адресу https://setuptools.pypa.io/en/latest/setuptools.html.
Обычно функция инициализации возвращает определение модуля, инициализированное с помощью PyModuleDef_Init(). Это позволяет разделить процесс создания на несколько этапов:
- Прежде чем будет выполнен какой-либо существенный код, Python может определить, какие возможности поддерживает модуль, и скорректировать окружение либо отказаться загружать несовместимое расширение.
- По умолчанию объект модуля создаёт сам Python — то есть для классов он выполняет эквивалент
object.__new__(). Он также задаёт начальные атрибуты, например__package__и__loader__. - Затем объект модуля инициализируется с помощью кода, специфичного для расширения, — эквивалента
__init__()для классов.
Это называется многоэтапной инициализацией, чтобы отличать её от устаревшей (но по-прежнему поддерживаемой) схемы одноэтапной инициализации, при которой функция инициализации возвращает полностью созданный модуль. Подробности см. в разделе «Одноэтапная инициализация» ниже.
Изменено в версии 3.5: Добавлена поддержка многоэтапной инициализации (PEP 489).
Несколько экземпляров модуля
По умолчанию модули расширения не являются синглтонами. Например, если удалить запись sys.modules и повторно импортировать модуль, будет создан новый объект модуля, обычно заполненный новыми объектами методов и типов. Старый модуль подвергается обычной сборке мусора. Это соответствует поведению модулей на чистом Python.
Дополнительные экземпляры модуля могут создаваться в субинтерпретаторах или после повторной инициализации среды выполнения Python (Py_Finalize() и Py_Initialize()). В этих случаях совместное использование объектов Python экземплярами модуля, вероятно, приведёт к сбоям или неопределённому поведению.
Чтобы избежать подобных проблем, каждый экземпляр модуля расширения должен быть изолирован: изменения одного экземпляра не должны неявно влиять на остальные, а всё состояние, принадлежащее модулю, включая ссылки на объекты Python, должно относиться к конкретному экземпляру модуля. Дополнительные сведения и практическое руководство см. в разделе «Изоляция модулей расширения».
Более простой способ избежать этих проблем — вызывать ошибку при повторной инициализации.
Предполагается, что все модули поддерживают субинтерпретаторы или явно сообщают об отсутствии такой поддержки. Обычно этого достигают изоляцией или блокировкой повторной инициализации, как описано выше. Модуль также можно ограничить основным интерпретатором с помощью слота Py_mod_multiple_interpreters.
Функция инициализации
Функция инициализации, определяемая модулем расширения, имеет следующую сигнатуру:
-
PyObject *PyInit_modulename(void)
Её имя должно быть PyInit_<name>, где <name> заменено именем модуля.
Для модулей, имена которых состоят только из ASCII-символов, функция должна вместо этого называться PyInit_<name>, где <name> заменено именем модуля. При использовании многоэтапной инициализации допускаются имена модулей, содержащие не-ASCII-символы. В этом случае имя функции инициализации — PyInitU_<name>, где <name> закодировано с помощью кодировки Python punycode, а дефисы заменены подчёркиваниями. В Python:
def initfunc_name(name):
try:
suffix = b'_' + name.encode('ascii')
except UnicodeEncodeError:
suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
return b'PyInit' + suffix
Рекомендуется определять функцию инициализации с помощью вспомогательного макроса:
-
PyMODINIT_FUNC -
Объявляет функцию инициализации модуля расширения. Этот макрос:
- задаёт тип возвращаемого значения PyObject*,
- добавляет все специальные объявления связывания, необходимые для платформы, и
- для C++ объявляет функцию как
extern "C".
Например, модуль с именем spam можно определить так:
static struct PyModuleDef spam_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "spam",
...
};
PyMODINIT_FUNC
PyInit_spam(void)
{
return PyModuleDef_Init(&spam_module);
}
Можно экспортировать несколько модулей из одной разделяемой библиотеки, определив несколько функций инициализации. Однако для их импорта необходимо использовать символические ссылки или пользовательский импортёр, поскольку по умолчанию находится только функция, соответствующая имени файла. Подробности см. в разделе «Несколько модулей в одной библиотеке» в PEP 489.
Функция инициализации обычно является единственным элементом, не относящимся к static, определённым в исходном файле C модуля.
Многоэтапная инициализация
Обычно функция инициализации (PyInit_modulename) возвращает экземпляр PyModuleDef с не-NULL m_slots. Перед возвратом экземпляр PyModuleDef необходимо инициализировать с помощью следующей функции:
-
PyObject *PyModuleDef_Init(PyModuleDef *def) -
Входит в стабильный ABI начиная с версии 3.5.
Гарантирует, что определение модуля является корректно инициализированным объектом Python, который правильно сообщает свой тип и счётчик ссылок.
Возвращает def, приведённый к типу
PyObject*, илиNULLв случае ошибки.Вызов этой функции необходим для многоэтапной инициализации. В других случаях её использовать не следует.
Обратите внимание, что Python предполагает, что структуры
PyModuleDefвыделяются статически. Эта функция может вернуть как новую, так и заимствованную ссылку; эту ссылку нельзя освобождать.Добавлено в версии 3.5.
Устаревшая одноэтапная инициализация
Внимание
Одноэтапная инициализация — устаревший механизм инициализации модулей расширения с известными недостатками и изъянами проектирования. Авторам модулей расширения рекомендуется вместо неё использовать многоэтапную инициализацию.
При одноэтапной инициализации функция инициализации (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().
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/c-api/extension-modules.html