> **Источник:** https://python-all.ru/3.16/c-api/extension-modules.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

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

Расширение на C для CPython – это разделяемая библиотека (например, файл `.so` в Linux, `.pyd` DLL в Windows), которая загружается в процесс Python (например, скомпилирована с совместимыми настройками компилятора) и экспортирует функцию *экспортного хука* (или старую [функцию инициализации](https://python-all.ru/3.16/c-api/extension-modules.html#extension-pyinit)).

Чтобы библиотека импортировалась по умолчанию (то есть с помощью [`importlib.machinery.ExtensionFileLoader`](https://python-all.ru/3.16/library/importlib.html#importlib.machinery.ExtensionFileLoader)), она должна быть доступна в [`sys.path`](https://python-all.ru/3.16/library/sys.html#sys.path), и её имя должно соответствовать имени модуля с расширением, указанным в [`importlib.machinery.EXTENSION_SUFFIXES`](https://python-all.ru/3.16/library/importlib.html#importlib.machinery.EXTENSION_SUFFIXES).

> **Примечание**
>
> Сборку, упаковку и распространение модулей расширения лучше выполнять с помощью сторонних инструментов, и это выходит за рамки данного документа. Подходящим инструментом является Setuptools, документация которого находится по адресу [https://setuptools.pypa.io/en/latest/setuptools.html](https://python-all.ru/3.16/c-api/extension-modules.html).

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

Добавлено в версии 3.15: Поддержка `PyModExport_<name>` экспортного хука была добавлена в Python 3.15. Старый способ определения модулей по-прежнему доступен: обратитесь к разделу [функции PyInit](https://python-all.ru/3.16/c-api/extension-modules.html#extension-pyinit) или к более ранним версиям этой документации, если вы планируете поддерживать более старые версии Python.

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

#### `PySlot *PyModExport_modulename(void)`

Для модулей, имена которых содержат только ASCII, [экспортный хук](https://python-all.ru/3.16/c-api/extension-modules.html#extension-export-hook) должен называться `PyModExport_<name>`, где `<name>` заменяется на имя модуля.

Для модулей с не-ASCII именами экспортный хук должен называться `PyModExportU_<name>` (обратите внимание на `U`), где `<name>` кодируется с помощью кодировки *punycode* Python, а дефисы заменяются на символы подчеркивания. В 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`](https://python-all.ru/3.16/c-api/slots.html#c.PySlot) записей, завершающийся записью с идентификатором слота `0`. Эти слоты описывают, как модуль должен быть создан и инициализирован.

Этот массив должен оставаться действительным и постоянным до завершения работы интерпретатора. Обычно он должен использовать хранилище `static`. Для любого динамического поведения предпочтительнее использовать слоты [`Py_mod_create`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_create) и [`Py_mod_exec`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_exec).

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

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

#### `PyMODEXPORT_FUNC`

*Часть [Stable ABI](https://python-all.ru/3.16/c-api/stable.html#stable) начиная с версии 3.15.*

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

- определяет тип возвращаемого значения [PySlot](https://python-all.ru/3.16/c-api/slots.html#c.PySlot)\*,
- добавляет любые специальные объявления компоновки, требуемые платформой, и
- для C++ объявляет функцию как `extern "C"`.

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

```c
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()`](https://python-all.ru/3.16/c-api/stable.html#c.PyABIInfo_Check), чтобы возбудить исключение, а не упасть, в распространённых случаях несовместимости ABI.
- Код в экспортном хуке никогда не должен полагаться на [GIL](https://python-all.ru/3.16/glossary.html#term-GIL): [сборки со свободными потоками](https://python-all.ru/3.16/glossary.html#term-free-threaded-build) Python могут проверять слот [`Py_mod_gil`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_gil) (или его отсутствие) только после возврата хука,
- Аналогично, хук может быть вызван в любом суб-интерпретаторе, так как слот [`Py_mod_multiple_interpreters`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_multiple_interpreters) (или его отсутствие) проверяется только после возврата хука.

Например:

```c
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](https://python-all.ru/3.16/c-api/extension-modules.html) section in [**PEP 489**](https://python-all.ru/3.16/c-api/extension-modules.html) for details.

## Многофазная инициализация

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

- Python находит и вызывает экспортный хук, чтобы получить информацию о том, как создать модуль.
- Перед выполнением любого существенного кода Python может определить, какие возможности поддерживает модуль, и может настроить окружение или отказаться загружать несовместимое расширение. Слоты, такие как [`Py_mod_abi`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_abi), [`Py_mod_gil`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_gil) и [`Py_mod_multiple_interpreters`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_multiple_interpreters), влияют на этот шаг.
- По умолчанию Python сам создаёт объект модуля – то есть делает эквивалент вызова [`__new__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__new__) при создании объекта. Этот шаг можно переопределить с помощью слота [`Py_mod_create`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_create).
- Python устанавливает начальные атрибуты модуля, такие как [`__package__`](https://python-all.ru/3.16/reference/datamodel.html#module.__package__) и [`__loader__`](https://python-all.ru/3.16/reference/datamodel.html#module.__loader__), и вставляет объект модуля в [`sys.modules`](https://python-all.ru/3.16/library/sys.html#sys.modules).
- После этого объект модуля инициализируется специфичным для расширения способом – эквивалентом [`__init__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__init__) при создании объекта или выполнения кода верхнего уровня в модуле на Python. Поведение задаётся с помощью слота [`Py_mod_exec`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_exec).

This is called *multi-phase initialization* to distinguish it from the legacy (but still supported) [single-phase initialization](https://python-all.ru/3.16/c-api/extension-modules.html#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**](https://python-all.ru/3.16/c-api/extension-modules.html)).

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

По умолчанию модули расширения не являются синглтонами. Например, если запись [`sys.modules`](https://python-all.ru/3.16/library/sys.html#sys.modules) удалена и модуль импортируется заново, создаётся новый объект модуля и, как правило, заполняется новыми объектами методов и типов. Старый модуль подлежит обычной сборке мусора. Это повторяет поведение чисто Python-модулей.

Additional module instances may be created in [sub-interpreters](https://python-all.ru/3.16/c-api/subinterpreters.html#sub-interpreter-support) or after Python runtime reinitialization ([`Py_Finalize()`](https://python-all.ru/3.16/c-api/interp-lifecycle.html#c.Py_Finalize) and [`Py_Initialize()`](https://python-all.ru/3.16/c-api/interp-lifecycle.html#c.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](https://python-all.ru/3.16/howto/isolating-extensions.html#isolating-extensions-howto) for more details and a practical guide.

A simpler way to avoid these issues is [raising an error on repeated initialization](https://python-all.ru/3.16/howto/isolating-extensions.html#isolating-extensions-optout).

All modules are expected to support [sub-interpreters](https://python-all.ru/3.16/c-api/subinterpreters.html#sub-interpreter-support), 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`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_multiple_interpreters) slot.

## `PyInit` функция

[Soft deprecated](https://python-all.ru/3.16/glossary.html#term-soft-deprecated) since version 3.15: This functionality will not get new features, but there are no plans to remove it.

Вместо [`PyModExport_modulename()`](https://python-all.ru/3.16/c-api/extension-modules.html#c.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](https://python-all.ru/3.16/c-api/extension-modules.html#extension-export-hook) (that is, using Punycode with underscores).

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

Как и в случае с [`PyMODEXPORT_FUNC`](https://python-all.ru/3.16/c-api/extension-modules.html#c.PyMODEXPORT_FUNC), рекомендуется определять функцию инициализации с помощью вспомогательного макроса:

#### `PyMODINIT_FUNC`

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

- задаёт возвращаемый тип [PyObject](https://python-all.ru/3.16/c-api/structures.html#c.PyObject)\*,
- добавляет любые специальные объявления компоновки, требуемые платформой, и
- для C++ объявляет функцию как `extern "C"`.

Обычно функция инициализации (`PyInit_modulename`) возвращает экземпляр [`PyModuleDef`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef) с не-`NULL` [`m_slots`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef.m_slots). Это позволяет Python использовать [многофазную инициализацию](https://python-all.ru/3.16/c-api/extension-modules.html#multi-phase-initialization).

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

#### `PyObject *PyModuleDef_Init(PyModuleDef *def)`

*Часть [Stable ABI](https://python-all.ru/3.16/c-api/stable.html#stable) начиная с версии 3.5.*

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

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

Вызов этой функции обязателен перед возвратом [`PyModuleDef`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef) из функции инициализации модуля. Не следует использовать её в других контекстах.

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

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

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

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

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

### Устаревшая однофазная инициализация

[Мягко устарело](https://python-all.ru/3.16/glossary.html#term-soft-deprecated) начиная с версии 3.15: Однофазная инициализация – это устаревший механизм инициализации модулей расширения с известными недостатками и ошибками проектирования. Авторам модулей расширения рекомендуется вместо этого использовать многофазную инициализацию.

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

При однофазной инициализации [функция инициализации старого образца](https://python-all.ru/3.16/c-api/extension-modules.html#extension-pyinit) (`PyInit_modulename`) должна создать, заполнить и вернуть объект модуля. Обычно это делается с помощью [`PyModule_Create()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_Create) и таких функций, как [`PyModule_AddObjectRef()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_AddObjectRef).

Однофазная инициализация отличается от [стандартной](https://python-all.ru/3.16/c-api/extension-modules.html#multi-phase-initialization) следующим образом:

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

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

  При последующих импортах Python не вызывает функцию инициализации снова. Вместо этого он создаёт новый объект модуля с новым `__dict__` и копирует в него сохранённое содержимое. Например, для однофазного модуля `_testsinglephase` [\[1\]](https://python-all.ru/3.16/c-api/extension-modules.html#testsinglephase), который определяет функцию `sum` и класс исключения `error`:

  ```python
  >>> 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()`](https://python-all.ru/3.16/c-api/module.html#c.PyState_FindModule).
- [`PyModuleDef.m_slots`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef.m_slots) модуля должен быть NULL.

\[[1](https://python-all.ru/3.16/c-api/extension-modules.html#id3)\]

`_testsinglephase` – это внутренний модуль, используемый в наборе тестов CPython; ваша установка может включать его, а может и нет.
