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

---

# Изоляция модулей расширений

Аннотация

Традиционно состояние модулей расширений Python хранилось в переменных C `static`, которые имели область видимости на весь процесс. В этом документе описываются проблемы такого состояния на процесс и показывается более безопасный подход: состояние на модуль.

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

## Кому следует это прочитать

Это руководство предназначено для мейнтейнеров расширений [C-API](https://python-all.ru/3.13/c-api/index.html#c-api-index), которые хотят сделать это расширение более безопасным в приложениях, где сам Python используется как библиотека.

## Предыстория

*Интерпретатор* – это среда выполнения кода Python. Он содержит конфигурацию (например, путь импорта) и состояние времени выполнения (например, набор импортированных модулей).

Python поддерживает запуск нескольких интерпретаторов в одном процессе. Рассмотрим два случая: пользователи могут запускать интерпретаторы

- последовательно, в несколько [`Py_InitializeEx()`](https://python-all.ru/3.13/c-api/init.html#c.Py_InitializeEx)/[`Py_FinalizeEx()`](https://python-all.ru/3.13/c-api/init.html#c.Py_FinalizeEx) циклов, и
- параллельно, управляя «субинтерпретаторами» с помощью [`Py_NewInterpreter()`](https://python-all.ru/3.13/c-api/init.html#c.Py_NewInterpreter)/[`Py_EndInterpreter()`](https://python-all.ru/3.13/c-api/init.html#c.Py_EndInterpreter).

Оба случая (и их комбинации) наиболее полезны при встраивании Python в библиотеку. Библиотеки обычно не должны строить предположения о приложении, которое их использует, в том числе предполагать наличие «главного интерпретатора Python» в рамках процесса.

Исторически сложилось, что расширения Python плохо обрабатывают этот вариант использования. Многие расширения (и даже некоторые модули из stdlib) используют *глобальное состояние процесса*, потому что C-переменные `static` чрезвычайно просты в использовании. Таким образом, данные, которые должны быть специфичны для конкретного интерпретатора, в итоге разделяются между интерпретаторами. Если разработчик расширения не будет внимателен, очень легко ввести краевые случаи, ведущие к аварийным завершениям, когда модуль загружается в нескольких интерпретаторах одного процесса.

К сожалению, *состояние на интерпретатор* достичь непросто. Авторы расширений обычно не думают о нескольких интерпретаторах при разработке, и в настоящее время тестировать такое поведение сложно.

### Состояние модуля

Вместо концентрации на состоянии на интерпретатор, C API Python развивается в сторону лучшей поддержки более детализированного *состояния на модуль*. Это означает, что данные уровня C должны быть привязаны к *объекту модуля*. Каждый интерпретатор создаёт свой собственный объект модуля, так что данные остаются разделёнными. Для проверки изоляции в одном интерпретаторе можно загружать даже несколько объектов модулей, соответствующих одному расширению.

Состояние на модуль даёт простой способ мыслить о времени жизни и владении ресурсами: расширение инициализируется при создании объекта модуля и очищается при его освобождении. В этом отношении модуль – это просто любой другой [PyObject](https://python-all.ru/3.13/c-api/structures.html#c.PyObject)\*; не нужно думать – или забывать – о хуках «при завершении интерпретатора».

Обратите внимание, что существуют варианты использования разных видов «глобальных» переменных: состояние на процесс, на интерпретатор, на поток или на задачу. При состоянии на модуль как умолчании они всё ещё возможны, но их следует рассматривать как исключительные случаи: если они нужны, нужно уделить им дополнительное внимание и тестирование. (Заметим, что данное руководство их не рассматривает.)

### Изолированные объекты модулей

Ключевой момент, который следует помнить при разработке расширения: из одной разделяемой библиотеки может быть создано несколько объектов модуля. Например:

```pycon
>>> import sys
>>> import binascii
>>> old_binascii = binascii
>>> del sys.modules['binascii']
>>> import binascii  # создать новый объект модуля
>>> old_binascii == binascii
False
```

Как правило, два модуля должны быть полностью независимы. Все объекты и состояние, специфичные для модуля, должны быть инкапсулированы внутри объекта модуля, не разделяться с другими объектами модулей и очищаться при освобождении объекта модуля. Поскольку это лишь общее правило, возможны исключения (см. раздел [Управление глобальным состоянием](https://python-all.ru/3.13/howto/isolating-extensions.html#managing-global-state)), но они потребуют больше размышлений и внимания к краевым случаям.

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

### Неожиданные краевые случаи

Заметьте, что изолированные модули действительно создают некоторые неожиданные краевые случаи. В частности, каждый объект модуля обычно не разделяет свои классы и исключения с другими аналогичными модулями. Продолжая [пример выше](https://python-all.ru/3.13/howto/isolating-extensions.html#isolated-module-objects), заметьте, что `old_binascii.Error` и `binascii.Error` – это разные объекты. В следующем коде исключение *не* перехватывается:

```pycon
>>> old_binascii.Error == binascii.Error
False
>>> try:
...     old_binascii.unhexlify(b'qwertyuiop')
... except binascii.Error:
...     print('boo')
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
binascii.Error: Non-hexadecimal digit found
```

Это ожидаемо. Заметьте, что чисто Python-модули ведут себя так же: это часть того, как работает Python.

Цель – сделать расширения безопасными на уровне C, а не заставлять «хаки» вести себя интуитивно. Изменение `sys.modules` «вручную» считается хаком.

## Обеспечение безопасности модулей при нескольких интерпретаторах

### Управление глобальным состоянием

Иногда состояние, связанное с модулем Python, не является специфичным для этого модуля, а относится ко всему процессу (или чему-то «более глобальному», чем модуль). Например:

- Модуль `readline` управляет *терминалом*.
- Модуль, работающий на плате, хочет управлять *встроенным* светодиодом.

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

Если необходимо использовать глобальное состояние процесса, самый простой способ избежать проблем с несколькими интерпретаторами – явно запретить загрузку модуля более одного раза на процесс – см. [Отказ: Ограничение одним объектом модуля на процесс](https://python-all.ru/3.13/howto/isolating-extensions.html#isolating-extensions-optout).

### Управление состоянием на модуль

Чтобы использовать состояние на модуль, используйте [многофазную инициализацию расширения](https://python-all.ru/3.13/c-api/module.html#multi-phase-initialization). Это сигнализирует, что ваш модуль корректно поддерживает несколько интерпретаторов.

Установите `PyModuleDef.m_size` в положительное число, чтобы запросить соответствующее количество байт локального хранилища для модуля. Обычно это устанавливается в размер некоторой `struct`, специфичной для модуля, которая может хранить всё C-уровневое состояние модуля. В частности, сюда следует помещать указатели на классы (включая исключения, но исключая статические типы) и настройки (например, `csv`’s [`field_size_limit`](https://python-all.ru/3.13/library/csv.html#csv.field_size_limit)), которые нужны C-коду для работы.

> **Примечание**
>
> Другой вариант – хранить состояние в `__dict__` модуля, но при этом нужно избегать аварийного завершения, когда пользователи изменяют `__dict__` из кода Python. Обычно это означает проверку ошибок и типов на уровне C, что легко сделать неправильно и сложно протестировать в достаточной мере.
>
> Однако, если состояние модуля не нужно в C-коде, хранение его только в `__dict__` – хорошая идея.

Если состояние модуля включает указатели `PyObject`, объект модуля должен удерживать ссылки на эти объекты и реализовывать хуки на уровне модуля `m_traverse`, `m_clear` и `m_free`. Они работают как `tp_traverse`, `tp_clear` и `tp_free` у класса. Их добавление потребует некоторой работы и сделает код длиннее; это цена за модули, которые можно чисто выгружать.

Пример модуля с состоянием уровня модуля в настоящее время доступен как [xxlimited](https://python-all.ru/3.13/howto/isolating-extensions.html); пример инициализации модуля приведён в нижней части файла.

### Отказ: ограничение одним объектом модуля на процесс

Неотрицательное значение `PyModuleDef.m_size` указывает, что модуль корректно поддерживает несколько интерпретаторов. Если ваш модуль этого ещё не делает, вы можете явно сделать его загружаемым только один раз на процесс. Например:

```c
// Флаг уровня процесса
static int loaded = 0;

// Мьютекс для обеспечения потокобезопасности (требуется только для free-threaded Python)
static PyMutex modinit_mutex = {0};

static int
exec_module(PyObject* module)
{
    PyMutex_Lock(&modinit_mutex);
    if (loaded) {
        PyMutex_Unlock(&modinit_mutex);
        PyErr_SetString(PyExc_ImportError,
                        "cannot load module more than once per process");
        return -1;
    }
    loaded = 1;
    PyMutex_Unlock(&modinit_mutex);
    // остальная часть инициализации
}
```

Если функция [`PyModuleDef.m_clear`](https://python-all.ru/3.13/c-api/module.html#c.PyModuleDef.m_clear) вашего модуля способна подготовиться к будущей повторной инициализации, она должна сбросить флаг `loaded`. В этом случае ваш модуль не будет поддерживать одновременное существование нескольких экземпляров *одновременно*, но он будет, например, поддерживать загрузку после завершения работы среды выполнения Python ([`Py_FinalizeEx()`](https://python-all.ru/3.13/c-api/init.html#c.Py_FinalizeEx)) и повторную инициализацию ([`Py_Initialize()`](https://python-all.ru/3.13/c-api/init.html#c.Py_Initialize)).

### Доступ к состоянию модуля из функций

Доступ к состоянию из функций уровня модуля не представляет сложности. Функции получают объект модуля в качестве первого аргумента; для извлечения состояния можно использовать `PyModule_GetState`:

```c
static PyObject *
func(PyObject *module, PyObject *args)
{
    my_struct *state = (my_struct*)PyModule_GetState(module);
    if (state == NULL) {
        return NULL;
    }
    // остальная часть логики
}
```

> **Примечание**
>
> `PyModule_GetState` может вернуть `NULL` без установки исключения, если состояния модуля нет, т.е. `PyModuleDef.m_size` был нулевым. В вашем модуле управление `m_size` принадлежит вам, так что это легко предотвратить.

## Кучные типы

Традиционно типы, определённые в коде на C, являются *статическими*; то есть это структуры `static PyTypeObject`, определяемые непосредственно в коде и инициализируемые с помощью `PyType_Ready()`.

Такие типы обязательно являются общими для всего процесса. Совместное использование их между объектами модулей требует внимания к любому состоянию, которым они владеют или к которому обращаются. Чтобы ограничить возможные проблемы, статические типы неизменяемы на уровне Python: например, нельзя задать `str.myattribute = 123`.

**Особенность реализации CPython:** Совместное использование по-настоящему неизменяемых объектов между интерпретаторами не вызывает проблем, пока они не предоставляют доступа к изменяемым объектам. Однако в CPython каждый объект Python содержит изменяемую деталь реализации: счётчик ссылок. Изменения счётчика ссылок защищены GIL. Таким образом, код, разделяющий любые объекты Python между интерпретаторами, неявно зависит от текущего, общепроцессного GIL в CPython.

Поскольку статические типы неизменяемы и глобальны для процесса, они не могут получить доступ к «своему» состоянию модуля. Если какой-либо метод такого типа требует доступа к состоянию модуля, тип необходимо преобразовать в *тип, размещаемый в куче*, или, сокращённо, *кучный тип*. Они больше соответствуют классам, создаваемым оператором `class` в Python.

Для новых модулей использование кучных типов по умолчанию является хорошим эмпирическим правилом.

### Преобразование статических типов в кучные типы

Статические типы можно преобразовать в кучные, но учтите, что API кучных типов не был разработан для преобразования «без потерь» из статических типов – то есть для создания типа, работающего в точности как данный статический тип. Поэтому при переписывании определения класса в новом API вы, вероятно, непреднамеренно измените некоторые детали (например, возможность сериализации или унаследованные слоты). Всегда тестируйте детали, важные для вас.

Обратите особое внимание на следующие два пункта (но учтите, что это не исчерпывающий список):

- В отличие от статических типов, объекты кучных типов по умолчанию изменяемы. Используйте флаг [`Py_TPFLAGS_IMMUTABLETYPE`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_IMMUTABLETYPE), чтобы предотвратить изменяемость.
- Кучные типы по умолчанию наследуют [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new), поэтому может стать возможным создание их экземпляров из кода Python. Вы можете предотвратить это с помощью флага [`Py_TPFLAGS_DISALLOW_INSTANTIATION`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_DISALLOW_INSTANTIATION).

### Определение кучных типов

Кучные типы можно создать, заполнив структуру [`PyType_Spec`](https://python-all.ru/3.13/c-api/type.html#c.PyType_Spec), которая является описанием или «чертежом» класса, и вызвав [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_FromModuleAndSpec) для создания нового объекта класса.

> **Примечание**
>
> Другие функции, такие как [`PyType_FromSpec()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_FromSpec), также могут создавать кучные типы, но [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_FromModuleAndSpec) связывает модуль с классом, предоставляя доступ к состоянию модуля из методов.

Класс обычно следует хранить *как в* состоянии модуля (для безопасного доступа из C), так и в `__dict__` модуля (для доступа из кода Python).

### Протокол сборки мусора

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

Чтобы избежать утечек памяти, экземпляры кучных типов должны реализовывать протокол сборки мусора. А именно, кучные типы должны:

- Иметь флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC).
- Определить функцию обхода с помощью `Py_tp_traverse`, которая посещает тип (например, с помощью `Py_VISIT(Py_TYPE(self))`).

Обратитесь к документации [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC) и [`tp_traverse`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_traverse) для дополнительных сведений.

API для определения кучных типов развивался органически, что сделало его несколько неудобным в использовании в текущем виде. Следующие разделы помогут вам разобраться с типичными проблемами.

#### `tp_traverse` в Python 3.8 и ниже

Требование посещать тип из `tp_traverse` было добавлено в Python 3.9. Если вы поддерживаете Python 3.8 и ниже, функция обхода *не должна* посещать тип, поэтому она должна быть более сложной:

```c
static int my_traverse(PyObject *self, visitproc visit, void *arg)
{
    if (Py_Version >= 0x03090000) {
        Py_VISIT(Py_TYPE(self));
    }
    return 0;
}
```

К сожалению, [`Py_Version`](https://python-all.ru/3.13/c-api/apiabiversion.html#c.Py_Version) был добавлен только в Python 3.11. В качестве замены используйте:

- [`PY_VERSION_HEX`](https://python-all.ru/3.13/c-api/apiabiversion.html#c.PY_VERSION_HEX), если не используется стабильный ABI, или
- [`sys.version_info`](https://python-all.ru/3.13/library/sys.html#sys.version_info) (через [`PySys_GetObject()`](https://python-all.ru/3.13/c-api/sys.html#c.PySys_GetObject) и [`PyArg_ParseTuple()`](https://python-all.ru/3.13/c-api/arg.html#c.PyArg_ParseTuple)).

#### Делегирование `tp_traverse`

Если функция обхода делегирует [`tp_traverse`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_traverse) своего базового класса (или другого типа), убедитесь, что `Py_TYPE(self)` посещается только один раз. Обратите внимание, что только тип кучи должен посещать тип в `tp_traverse`.

Например, если ваша функция обхода включает:

```c
base->tp_traverse(self, visit, arg)
```

…и `base` может быть статическим типом, тогда она также должна включать:

```c
if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
    // tp_traverse типа‑кучи уже посетил Py_TYPE(self)
} else {
    if (Py_Version >= 0x03090000) {
        Py_VISIT(Py_TYPE(self));
    }
}
```

Нет необходимости обрабатывать счетчик ссылок типа в [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_clear`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_clear).

#### Определение `tp_dealloc`

Если ваш тип имеет пользовательскую функцию [`tp_dealloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), она должна:

- вызывать [`PyObject_GC_UnTrack()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.PyObject_GC_UnTrack) до того, как будут инвалидированы любые поля, и
- уменьшить счетчик ссылок типа.

Чтобы тип оставался валидным во время вызова `tp_free`, счетчик ссылок типа должен быть уменьшен *после* освобождения экземпляра. Например:

```c
static void my_dealloc(PyObject *self)
{
    PyObject_GC_UnTrack(self);
    ...
    PyTypeObject *type = Py_TYPE(self);
    type->tp_free(self);
    Py_DECREF(type);
}
```

Функция `tp_dealloc` по умолчанию делает это, поэтому если ваш тип *не* переопределяет `tp_dealloc`, вы можете не добавлять её.

#### Не переопределение `tp_free`

Слот [`tp_free`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_free) типа кучи должен быть установлен в [`PyObject_GC_Del()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.PyObject_GC_Del). Это значение по умолчанию; не переопределяйте его.

#### Избегание `PyObject_New`

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

Если вы используете [`PyObject_New()`](https://python-all.ru/3.13/c-api/allocation.html#c.PyObject_New) или [`PyObject_NewVar()`](https://python-all.ru/3.13/c-api/allocation.html#c.PyObject_NewVar):

- Получить и вызвать слот [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc) типа, если возможно. То есть заменить `TYPE *o = PyObject_New(TYPE, typeobj)` на:

  ```c
  TYPE *o = typeobj->tp_alloc(typeobj, 0);
  ```

  Заменить `o = PyObject_NewVar(TYPE, typeobj, size)` на то же самое, но использовать size вместо 0.
- Если вышеуказанное невозможно (например, внутри пользовательского `tp_alloc`), вызовите [`PyObject_GC_New()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.PyObject_GC_New) или [`PyObject_GC_NewVar()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.PyObject_GC_NewVar):

  ```c
  TYPE *o = PyObject_GC_New(TYPE, typeobj);

  TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, size);
  ```

### Доступ к состоянию модуля из классов

Если у вас есть объект типа, определенный с [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_FromModuleAndSpec), вы можете вызвать [`PyType_GetModule()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GetModule), чтобы получить связанный модуль, а затем [`PyModule_GetState()`](https://python-all.ru/3.13/c-api/module.html#c.PyModule_GetState), чтобы получить состояние модуля.

Чтобы избавиться от утомительного шаблонного кода обработки ошибок, вы можете объединить эти два шага с помощью [`PyType_GetModuleState()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GetModuleState), получив:

```c
my_struct *state = (my_struct*)PyType_GetModuleState(type);
if (state == NULL) {
    return NULL;
}
```

### Доступ к состоянию модуля из обычных методов

Доступ к состоянию на уровне модуля из методов класса несколько сложнее, но возможен благодаря API, введенному в Python 3.9. Чтобы получить состояние, сначала нужно получить *определяющий класс*, а затем получить из него состояние модуля.

Самое большое препятствие – получение *класса, в котором был определен метод*, или, сокращенно, «определяющего класса» этого метода. Определяющий класс может содержать ссылку на модуль, частью которого он является.

Не путайте определяющий класс с `Py_TYPE(self)`. Если метод вызывается для *подкласса* вашего типа, `Py_TYPE(self)` будет ссылаться на этот подкласс, который может быть определен в другом модуле, отличном от вашего.

> **Примечание**
>
> Следующий код на Python может проиллюстрировать концепцию. `Base.get_defining_class` возвращает `Base`, даже если `type(self) == Sub`:
>
> ```python
> class Base:
>     def get_type_of_self(self):
>         return type(self)
>
>     def get_defining_class(self):
>         return __class__
>
> class Sub(Base):
>     pass
> ```

Чтобы метод получил свой «определяющий класс», он должен использовать [METH\_METHOD | METH\_FASTCALL | METH\_KEYWORDS](https://python-all.ru/3.13/c-api/structures.html#meth-method-meth-fastcall-meth-keywords) [`calling convention`](https://python-all.ru/3.13/c-api/structures.html#c.PyMethodDef) и соответствующую сигнатуру [`PyCMethod`](https://python-all.ru/3.13/c-api/structures.html#c.PyCMethod):

```c
PyObject *PyCMethod(
    PyObject *self,               // объект, на котором вызван метод
    PyTypeObject *defining_class, // определяющий класс
    PyObject *const *args,        // C‑массив аргументов
    Py_ssize_t nargs,             // длина "args"
    PyObject *kwnames)            // NULL или словарь именованных аргументов
```

Получив определяющий класс, вызовите [`PyType_GetModuleState()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GetModuleState), чтобы получить состояние связанного с ним модуля.

Например:

```c
static PyObject *
example_method(PyObject *self,
        PyTypeObject *defining_class,
        PyObject *const *args,
        Py_ssize_t nargs,
        PyObject *kwnames)
{
    my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);
    if (state == NULL) {
        return NULL;
    }
    ... // остальная логика
}

PyDoc_STRVAR(example_method_doc, "...");

static PyMethodDef my_methods[] = {
    {"example_method",
      (PyCFunction)(void(*)(void))example_method,
      METH_METHOD|METH_FASTCALL|METH_KEYWORDS,
      example_method_doc}
    {NULL},
}
```

### Доступ к состоянию модуля из слотовых методов, геттеров и сеттеров

> **Примечание**
>
> Это новое в Python 3.11.

Слот-методы – быстрые C-эквиваленты специальных методов, такие как [`nb_add`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyNumberMethods.nb_add) для [`__add__`](https://python-all.ru/3.13/reference/datamodel.html#object.__add__) или [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new) для инициализации – имеют очень простой API, который не позволяет передавать определяющий класс, в отличие от [`PyCMethod`](https://python-all.ru/3.13/c-api/structures.html#c.PyCMethod). То же самое относится к геттерам и сеттерам, определённым с помощью [`PyGetSetDef`](https://python-all.ru/3.13/c-api/structures.html#c.PyGetSetDef).

Чтобы получить состояние модуля в таких случаях, используйте функцию [`PyType_GetModuleByDef()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GetModuleByDef) и передайте определение модуля. Получив модуль, вызовите [`PyModule_GetState()`](https://python-all.ru/3.13/c-api/module.html#c.PyModule_GetState), чтобы получить состояние:

```c
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);
my_struct *state = (my_struct*)PyModule_GetState(module);
if (state == NULL) {
    return NULL;
}
```

`PyType_GetModuleByDef()` работает путём поиска в [порядке разрешения методов](https://python-all.ru/3.13/glossary.html#term-method-resolution-order) (то есть во всех суперклассах) первого суперкласса, у которого есть соответствующий модуль.

> **Примечание**
>
> В очень необычных случаях (цепочки наследования, охватывающие несколько модулей, созданных из одного определения) `PyType_GetModuleByDef()` может не вернуть модуль истинного определяющего класса. Однако он всегда будет возвращать модуль с тем же определением, что гарантирует совместимую структуру памяти C.

### Время жизни состояния модуля

Когда объект модуля собирается сборщиком мусора, его состояние освобождается. Для каждого указателя на (часть) состояния модуля необходимо удерживать ссылку на объект модуля.

Обычно это не проблема, потому что типы, созданные с помощью [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_FromModuleAndSpec), и их экземпляры хранят ссылку на модуль. Однако нужно быть осторожным с подсчётом ссылок, когда вы ссылаетесь на состояние модуля из других мест, например, из колбэков внешних библиотек.

## Открытые вопросы

Несколько вопросов, связанных с состоянием на модуль и кучевыми типами, всё ещё открыты.

Обсуждения по улучшению ситуации лучше всего вести на [форуме discuss под тегом c-api](https://python-all.ru/3.13/howto/isolating-extensions.html).

### Область видимости для класса

На данный момент (начиная с Python 3.11) невозможно привязать состояние к отдельным *типам*, не полагаясь на детали реализации CPython (которые могут измениться в будущем – возможно, по иронии судьбы, чтобы обеспечить правильное решение для области видимости на класс).

### Преобразование в кучевые типы без потерь

API кучевых типов не был разработан для «без потерь» преобразования из статических типов; то есть для создания типа, который работает точно так же, как заданный статический тип.
