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

isolating-extensions.md

405 строк · 38.4 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.11/howto/isolating-extensions.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Изоляция модулей расширений89Аннотация1011Традиционно состояние модулей расширений Python хранилось в переменных C `static`, которые имели область видимости на весь процесс. В этом документе описываются проблемы такого состояния на процесс и показывается более безопасный подход: состояние на модуль.1213В документе также описывается, как перейти на состояние на модуль там, где это возможно. Этот переход включает выделение памяти под это состояние, возможно, переключение со статических типов на типы кучи и – что, возможно, наиболее важно – доступ к состоянию на модуль из кода.1415## Кому следует это прочитать1617Это руководство предназначено для мейнтейнеров расширений [C-API](https://python-all.ru/3.11/c-api/index.html#c-api-index), которые хотят сделать это расширение более безопасным в приложениях, где сам Python используется как библиотека.1819## Предыстория2021*Интерпретатор* – это среда выполнения кода Python. Он содержит конфигурацию (например, путь импорта) и состояние времени выполнения (например, набор импортированных модулей).2223Python поддерживает запуск нескольких интерпретаторов в одном процессе. Рассмотрим два случая: пользователи могут запускать интерпретаторы2425- последовательно, в несколько [`Py_InitializeEx()`](https://python-all.ru/3.11/c-api/init.html#c.Py_InitializeEx)/[`Py_FinalizeEx()`](https://python-all.ru/3.11/c-api/init.html#c.Py_FinalizeEx) циклов, и26- параллельно, управляя «субинтерпретаторами» с помощью [`Py_NewInterpreter()`](https://python-all.ru/3.11/c-api/init.html#c.Py_NewInterpreter)/[`Py_EndInterpreter()`](https://python-all.ru/3.11/c-api/init.html#c.Py_EndInterpreter).2728Оба случая (и их комбинации) наиболее полезны при встраивании Python в библиотеку. Библиотеки обычно не должны строить предположения о приложении, которое их использует, в том числе предполагать наличие «главного интерпретатора Python» в рамках процесса.2930Исторически сложилось, что расширения Python плохо обрабатывают этот вариант использования. Многие расширения (и даже некоторые модули из stdlib) используют *глобальное состояние процесса*, потому что C-переменные `static` чрезвычайно просты в использовании. Таким образом, данные, которые должны быть специфичны для конкретного интерпретатора, в итоге разделяются между интерпретаторами. Если разработчик расширения не будет внимателен, очень легко ввести краевые случаи, ведущие к аварийным завершениям, когда модуль загружается в нескольких интерпретаторах одного процесса.3132К сожалению, *состояние на интерпретатор* достичь непросто. Авторы расширений обычно не думают о нескольких интерпретаторах при разработке, и в настоящее время тестировать такое поведение сложно.3334### Состояние модуля3536Вместо акцента на состоянии на уровне интерпретатора C API Python развивается в сторону более гранулярной поддержки *состояния на уровне модуля*. Это означает, что данные на уровне C привязываются к *объекту модуля*. Каждый интерпретатор создаёт собственный объект модуля, благодаря чему данные остаются разделёнными. Для проверки изоляции в одном интерпретаторе можно даже загрузить несколько объектов модуля, соответствующих одному расширению.3738Состояние на модуль даёт простой способ мыслить о времени жизни и владении ресурсами: расширение инициализируется при создании объекта модуля и очищается при его освобождении. В этом отношении модуль – это просто любой другой [PyObject](https://python-all.ru/3.11/c-api/structures.html#c.PyObject)\*; не нужно думать – или забывать – о хуках «при завершении интерпретатора».3940Обратите внимание, что существуют варианты использования разных видов «глобальных» переменных: состояние на процесс, на интерпретатор, на поток или на задачу. При состоянии на модуль как умолчании они всё ещё возможны, но их следует рассматривать как исключительные случаи: если они нужны, нужно уделить им дополнительное внимание и тестирование. (Заметим, что данное руководство их не рассматривает.)4142### Изолированные объекты модулей4344Ключевой момент, который следует помнить при разработке расширения: из одной разделяемой библиотеки может быть создано несколько объектов модуля. Например:4546```pycon47>>> import sys48>>> import binascii49>>> old_binascii = binascii50>>> del sys.modules['binascii']51>>> import binascii  # создать новый объект модуля52>>> old_binascii == binascii53False54```5556Как правило, два модуля должны быть полностью независимы. Все объекты и состояние, специфичные для модуля, должны быть инкапсулированы внутри объекта модуля, не разделяться с другими объектами модулей и очищаться при освобождении объекта модуля. Поскольку это лишь общее правило, возможны исключения (см. раздел [Управление глобальным состоянием](https://python-all.ru/3.11/howto/isolating-extensions.html#managing-global-state)), но они потребуют больше размышлений и внимания к краевым случаям.5758Хотя некоторые модули могли бы обойтись менее строгими ограничениями, изолированные модули упрощают установление чётких ожиданий и правил, работающих для множества вариантов использования.5960### Неожиданные краевые случаи6162Заметьте, что изолированные модули действительно создают некоторые неожиданные краевые случаи. В частности, каждый объект модуля обычно не разделяет свои классы и исключения с другими аналогичными модулями. Продолжая [пример выше](https://python-all.ru/3.11/howto/isolating-extensions.html#isolated-module-objects), заметьте, что `old_binascii.Error` и `binascii.Error` – это разные объекты. В следующем коде исключение *не* перехватывается:6364```pycon65>>> old_binascii.Error == binascii.Error66False67>>> try:68...     old_binascii.unhexlify(b'qwertyuiop')69... except binascii.Error:70...     print('boo')71...72Traceback (most recent call last):73  File "<stdin>", line 2, in <module>74binascii.Error: Non-hexadecimal digit found75```7677Это ожидаемо. Заметьте, что чисто Python-модули ведут себя так же: это часть того, как работает Python.7879Цель – сделать расширения безопасными на уровне C, а не заставлять «хаки» вести себя интуитивно. Изменение `sys.modules` «вручную» считается хаком.8081## Обеспечение безопасности модулей при нескольких интерпретаторах8283### Управление глобальным состоянием8485Иногда состояние, связанное с модулем Python, не является специфичным для этого модуля, а относится ко всему процессу (или чему-то «более глобальному», чем модуль). Например:8687- Модуль `readline` управляет *терминалом*.88- Модуль, работающий на плате, хочет управлять *встроенным* светодиодом.8990В таких случаях модуль Python должен предоставлять *доступ* к глобальному состоянию, а не *владеть* им. Если возможно, напишите модуль так, чтобы несколько его копий могли обращаться к состоянию независимо (наряду с другими библиотеками, будь то для Python или других языков). Если это невозможно, рассмотрите явную блокировку.9192Если необходимо использовать глобальное состояние процесса, самый простой способ избежать проблем с несколькими интерпретаторами – явно запретить загрузку модуля более одного раза на процесс – см. [Отказ: ограничение до одного объекта модуля на процесс](https://python-all.ru/3.11/howto/isolating-extensions.html#opt-out-limiting-to-one-module-object-per-process).9394### Управление состоянием на модуль9596Чтобы использовать состояние на модуль, используйте [многофазную инициализацию расширения](https://python-all.ru/3.11/c-api/module.html#multi-phase-initialization). Это сигнализирует, что ваш модуль корректно поддерживает несколько интерпретаторов.9798Установите `PyModuleDef.m_size` в положительное число, чтобы запросить соответствующее количество байт локального хранилища для модуля. Обычно это устанавливается в размер некоторой `struct`, специфичной для модуля, которая может хранить всё C-уровневое состояние модуля. В частности, сюда следует помещать указатели на классы (включая исключения, но исключая статические типы) и настройки (например, `csv`’s [`field_size_limit`](https://python-all.ru/3.11/library/csv.html#csv.field_size_limit)), которые нужны C-коду для работы.99100> **Примечание**101>102> Другой вариант – хранить состояние в `__dict__` модуля, но при этом нужно избегать аварийного завершения, когда пользователи изменяют `__dict__` из кода Python. Обычно это означает проверку ошибок и типов на уровне C, что легко сделать неправильно и сложно протестировать в достаточной мере.103>104> Однако, если состояние модуля не нужно в C-коде, хранение его только в `__dict__` – хорошая идея.105106Если состояние модуля включает указатели `PyObject`, объект модуля должен удерживать ссылки на эти объекты и реализовывать хуки на уровне модуля `m_traverse`, `m_clear` и `m_free`. Они работают как `tp_traverse`, `tp_clear` и `tp_free` у класса. Их добавление потребует некоторой работы и сделает код длиннее; это цена за модули, которые можно чисто выгружать.107108Пример модуля с состоянием уровня модуля в настоящее время доступен как [xxlimited](https://python-all.ru/3.11/howto/isolating-extensions.html); пример инициализации модуля приведён в нижней части файла.109110### Отказ: ограничение одним объектом модуля на процесс111112Неотрицательное значение `PyModuleDef.m_size` указывает, что модуль корректно поддерживает несколько интерпретаторов. Если ваш модуль этого ещё не делает, вы можете явно сделать его загружаемым только один раз на процесс. Например:113114```c115static int loaded = 0;116117static int118exec_module(PyObject* module)119{120    if (loaded) {121        PyErr_SetString(PyExc_ImportError,122                        "cannot load module more than once per process");123        return -1;124    }125    loaded = 1;126    // остальная часть инициализации127}128```129130### Доступ к состоянию модуля из функций131132Доступ к состоянию из функций уровня модуля не представляет сложности. Функции получают объект модуля в качестве первого аргумента; для извлечения состояния можно использовать `PyModule_GetState`:133134```c135static PyObject *136func(PyObject *module, PyObject *args)137{138    my_struct *state = (my_struct*)PyModule_GetState(module);139    if (state == NULL) {140        return NULL;141    }142    // остальная часть логики143}144```145146> **Примечание**147>148> `PyModule_GetState` может вернуть `NULL` без установки исключения, если состояния модуля нет, т.е. `PyModuleDef.m_size` был нулевым. В вашем модуле управление `m_size` принадлежит вам, так что это легко предотвратить.149150## Кучные типы151152Традиционно типы, определённые в коде на C, являются *статическими*; то есть это структуры `static PyTypeObject`, определяемые непосредственно в коде и инициализируемые с помощью `PyType_Ready()`.153154Такие типы обязательно являются общими для всего процесса. Совместное использование их между объектами модулей требует внимания к любому состоянию, которым они владеют или к которому обращаются. Чтобы ограничить возможные проблемы, статические типы неизменяемы на уровне Python: например, нельзя задать `str.myattribute = 123`.155156**Особенность реализации CPython:** Совместное использование по-настоящему неизменяемых объектов между интерпретаторами не вызывает проблем, пока они не предоставляют доступа к изменяемым объектам. Однако в CPython каждый объект Python содержит изменяемую деталь реализации: счётчик ссылок. Изменения счётчика ссылок защищены GIL. Таким образом, код, разделяющий любые объекты Python между интерпретаторами, неявно зависит от текущего, общепроцессного GIL в CPython.157158Поскольку статические типы неизменяемы и глобальны для процесса, они не могут получить доступ к «своему» состоянию модуля. Если какой-либо метод такого типа требует доступа к состоянию модуля, тип необходимо преобразовать в *тип, размещаемый в куче*, или, сокращённо, *кучный тип*. Они больше соответствуют классам, создаваемым оператором `class` в Python.159160Для новых модулей использование кучных типов по умолчанию является хорошим эмпирическим правилом.161162### Преобразование статических типов в кучные типы163164Статические типы можно преобразовать в кучные, но учтите, что API кучных типов не был разработан для преобразования «без потерь» из статических типов – то есть для создания типа, работающего в точности как данный статический тип. Поэтому при переписывании определения класса в новом API вы, вероятно, непреднамеренно измените некоторые детали (например, возможность сериализации или унаследованные слоты). Всегда тестируйте детали, важные для вас.165166Обратите особое внимание на следующие два пункта (но учтите, что это не исчерпывающий список):167168- В отличие от статических типов, объекты кучных типов по умолчанию изменяемы. Используйте флаг [`Py_TPFLAGS_IMMUTABLETYPE`](https://python-all.ru/3.11/c-api/typeobj.html#c.Py_TPFLAGS_IMMUTABLETYPE), чтобы предотвратить изменяемость.169- Кучные типы по умолчанию наследуют [`tp_new`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_new), поэтому может стать возможным создание их экземпляров из кода Python. Вы можете предотвратить это с помощью флага [`Py_TPFLAGS_DISALLOW_INSTANTIATION`](https://python-all.ru/3.11/c-api/typeobj.html#c.Py_TPFLAGS_DISALLOW_INSTANTIATION).170171### Определение кучных типов172173Кучные типы можно создать, заполнив структуру [`PyType_Spec`](https://python-all.ru/3.11/c-api/type.html#c.PyType_Spec), которая является описанием или «чертежом» класса, и вызвав [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_FromModuleAndSpec) для создания нового объекта класса.174175> **Примечание**176>177> Другие функции, такие как [`PyType_FromSpec()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_FromSpec), также могут создавать кучные типы, но [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_FromModuleAndSpec) связывает модуль с классом, предоставляя доступ к состоянию модуля из методов.178179Класс обычно следует хранить *как в* состоянии модуля (для безопасного доступа из C), так и в `__dict__` модуля (для доступа из кода Python).180181### Протокол сборки мусора182183Экземпляры кучных типов содержат ссылку на свой тип. Это гарантирует, что тип не будет уничтожен до того, как будут уничтожены все его экземпляры, но может привести к циклам ссылок, которые должен разорвать сборщик мусора.184185Чтобы избежать утечек памяти, экземпляры кучных типов должны реализовывать протокол сборки мусора. А именно, кучные типы должны:186187- Иметь флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.11/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC).188- Определить функцию обхода с помощью `Py_tp_traverse`, которая посещает тип (например, с помощью `Py_VISIT(Py_TYPE(self))`).189190Обратитесь к документации [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.11/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC) и [`tp_traverse`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse) для получения дополнительных сведений.191192API для определения кучных типов развивался органически, что сделало его несколько неудобным в использовании в текущем виде. Следующие разделы помогут вам разобраться с типичными проблемами.193194#### `tp_traverse` в Python 3.8 и ниже195196Требование посещать тип из `tp_traverse` было добавлено в Python 3.9. Если вы поддерживаете Python 3.8 и ниже, функция обхода *не должна* посещать тип, поэтому она должна быть более сложной:197198```c199static int my_traverse(PyObject *self, visitproc visit, void *arg)200{201    if (Py_Version >= 0x03090000) {202        Py_VISIT(Py_TYPE(self));203    }204    return 0;205}206```207208К сожалению, [`Py_Version`](https://python-all.ru/3.11/c-api/apiabiversion.html#c.Py_Version) был добавлен только в Python 3.11. В качестве замены используйте:209210- [`PY_VERSION_HEX`](https://python-all.ru/3.11/c-api/apiabiversion.html#c.PY_VERSION_HEX), если не используется стабильный ABI, или211- [`sys.version_info`](https://python-all.ru/3.11/library/sys.html#sys.version_info) (через [`PySys_GetObject()`](https://python-all.ru/3.11/c-api/sys.html#c.PySys_GetObject) и [`PyArg_ParseTuple()`](https://python-all.ru/3.11/c-api/arg.html#c.PyArg_ParseTuple)).212213#### Делегирование `tp_traverse`214215Если функция обхода делегирует [`tp_traverse`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse) своего базового класса (или другого типа), убедитесь, что `Py_TYPE(self)` посещается только один раз. Обратите внимание, что только тип кучи должен посещать тип в `tp_traverse`.216217Например, если ваша функция обхода включает:218219```c220base->tp_traverse(self, visit, arg)221```222223…и `base` может быть статическим типом, тогда она также должна включать:224225```c226if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {227    // tp_traverse типа‑кучи уже посетил Py_TYPE(self)228} else {229    if (Py_Version >= 0x03090000) {230        Py_VISIT(Py_TYPE(self));231    }232}233```234235Нет необходимости обрабатывать счетчик ссылок типа в [`tp_new`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_clear`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_clear).236237#### Определение `tp_dealloc`238239Если ваш тип имеет пользовательскую функцию [`tp_dealloc`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), она должна:240241- вызывать [`PyObject_GC_UnTrack()`](https://python-all.ru/3.11/c-api/gcsupport.html#c.PyObject_GC_UnTrack) до того, как будут инвалидированы любые поля, и242- уменьшить счетчик ссылок типа.243244Чтобы тип оставался валидным во время вызова `tp_free`, счетчик ссылок типа должен быть уменьшен *после* освобождения экземпляра. Например:245246```c247static void my_dealloc(PyObject *self)248{249    PyObject_GC_UnTrack(self);250    ...251    PyTypeObject *type = Py_TYPE(self);252    type->tp_free(self);253    Py_DECREF(type);254}255```256257Функция `tp_dealloc` по умолчанию делает это, поэтому если ваш тип *не* переопределяет `tp_dealloc`, вы можете не добавлять её.258259#### Не переопределение `tp_free`260261Слот [`tp_free`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_free) типа кучи должен быть установлен в [`PyObject_GC_Del()`](https://python-all.ru/3.11/c-api/gcsupport.html#c.PyObject_GC_Del). Это значение по умолчанию; не переопределяйте его.262263#### Избегание `PyObject_New`264265Объекты, отслеживаемые сборщиком мусора, должны выделяться с помощью функций, учитывающих сборщик мусора.266267Если используется [`PyObject_New()`](https://python-all.ru/3.11/c-api/allocation.html#c.PyObject_New) или [`PyObject_NewVar()`](https://python-all.ru/3.11/c-api/allocation.html#c.PyObject_NewVar):268269- Получить и вызвать слот [`tp_alloc`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_alloc) типа, если возможно. То есть заменить `TYPE *o = PyObject_New(TYPE, typeobj)` на:270271  ```c272  TYPE *o = typeobj->tp_alloc(typeobj, 0);273  ```274275  Заменить `o = PyObject_NewVar(TYPE, typeobj, size)` на то же самое, но использовать size вместо 0.276- Если вышеуказанное невозможно (например, внутри пользовательского `tp_alloc`), вызовите [`PyObject_GC_New()`](https://python-all.ru/3.11/c-api/gcsupport.html#c.PyObject_GC_New) или [`PyObject_GC_NewVar()`](https://python-all.ru/3.11/c-api/gcsupport.html#c.PyObject_GC_NewVar):277278  ```c279  TYPE *o = PyObject_GC_New(TYPE, typeobj);280281  TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, size);282  ```283284### Доступ к состоянию модуля из классов285286Если у вас есть объект типа, определенный с [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_FromModuleAndSpec), вы можете вызвать [`PyType_GetModule()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_GetModule), чтобы получить связанный модуль, а затем [`PyModule_GetState()`](https://python-all.ru/3.11/c-api/module.html#c.PyModule_GetState), чтобы получить состояние модуля.287288Чтобы избавиться от утомительного шаблонного кода обработки ошибок, вы можете объединить эти два шага с помощью [`PyType_GetModuleState()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_GetModuleState), получив:289290```c291my_struct *state = (my_struct*)PyType_GetModuleState(type);292if (state === NULL) {293    return NULL;294}295```296297### Доступ к состоянию модуля из обычных методов298299Доступ к состоянию на уровне модуля из методов класса несколько сложнее, но возможен благодаря API, введенному в Python 3.9. Чтобы получить состояние, сначала нужно получить *определяющий класс*, а затем получить из него состояние модуля.300301Самое большое препятствие – получение *класса, в котором был определен метод*, или, сокращенно, «определяющего класса» этого метода. Определяющий класс может содержать ссылку на модуль, частью которого он является.302303Не путайте определяющий класс с `Py_TYPE(self)`. Если метод вызывается для *подкласса* вашего типа, `Py_TYPE(self)` будет ссылаться на этот подкласс, который может быть определен в другом модуле, отличном от вашего.304305> **Примечание**306>307> Следующий код на Python может проиллюстрировать концепцию. `Base.get_defining_class` возвращает `Base`, даже если `type(self) == Sub`:308>309> ```python310> class Base:311>     def get_type_of_self(self):312>         return type(self)313>314>     def get_defining_class(self):315>         return __class__316>317> class Sub(Base):318>     pass319> ```320321Чтобы метод получил свой «определяющий класс», он должен использовать [METH\_METHOD | METH\_FASTCALL | METH\_KEYWORDS](https://python-all.ru/3.11/c-api/structures.html#meth-method-meth-fastcall-meth-keywords) [`calling convention`](https://python-all.ru/3.11/c-api/structures.html#c.PyMethodDef) и соответствующую сигнатуру [`PyCMethod`](https://python-all.ru/3.11/c-api/structures.html#c.PyCMethod):322323```c324PyObject *PyCMethod(325    PyObject *self,               // объект, на котором вызван метод326    PyTypeObject *defining_class, // определяющий класс327    PyObject *const *args,        // C‑массив аргументов328    Py_ssize_t nargs,             // длина "args"329    PyObject *kwnames)            // NULL или словарь именованных аргументов330```331332Получив определяющий класс, вызовите [`PyType_GetModuleState()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_GetModuleState), чтобы получить состояние связанного с ним модуля.333334Например:335336```c337static PyObject *338example_method(PyObject *self,339        PyTypeObject *defining_class,340        PyObject *const *args,341        Py_ssize_t nargs,342        PyObject *kwnames)343{344    my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);345    if (state === NULL) {346        return NULL;347    }348    ... // остальная логика349}350351PyDoc_STRVAR(example_method_doc, "...");352353static PyMethodDef my_methods[] = {354    {"example_method",355      (PyCFunction)(void(*)(void))example_method,356      METH_METHOD|METH_FASTCALL|METH_KEYWORDS,357      example_method_doc}358    {NULL},359}360```361362### Доступ к состоянию модуля из слотовых методов, геттеров и сеттеров363364> **Примечание**365>366> Это новое в Python 3.11.367368Слот-методы – быстрые C-эквиваленты специальных методов, такие как [`nb_add`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyNumberMethods.nb_add) для [`__add__`](https://python-all.ru/3.11/reference/datamodel.html#object.__add__) или [`tp_new`](https://python-all.ru/3.11/c-api/typeobj.html#c.PyTypeObject.tp_new) для инициализации – имеют очень простой API, который не позволяет передавать определяющий класс, в отличие от [`PyCMethod`](https://python-all.ru/3.11/c-api/structures.html#c.PyCMethod). То же самое относится к геттерам и сеттерам, определённым с помощью [`PyGetSetDef`](https://python-all.ru/3.11/c-api/structures.html#c.PyGetSetDef).369370Чтобы получить состояние модуля в таких случаях, используйте функцию [`PyType_GetModuleByDef()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_GetModuleByDef) и передайте определение модуля. Получив модуль, вызовите [`PyModule_GetState()`](https://python-all.ru/3.11/c-api/module.html#c.PyModule_GetState), чтобы получить состояние:371372```c373PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);374my_struct *state = (my_struct*)PyModule_GetState(module);375if (state === NULL) {376    return NULL;377}378```379380`PyType_GetModuleByDef()` работает путём поиска в [порядке разрешения методов](https://python-all.ru/3.11/glossary.html#term-method-resolution-order) (то есть во всех суперклассах) первого суперкласса, у которого есть соответствующий модуль.381382> **Примечание**383>384> В очень необычных случаях (цепочки наследования, охватывающие несколько модулей, созданных из одного определения) `PyType_GetModuleByDef()` может не вернуть модуль истинного определяющего класса. Однако он всегда будет возвращать модуль с тем же определением, что гарантирует совместимую структуру памяти C.385386### Время жизни состояния модуля387388Когда объект модуля собирается сборщиком мусора, его состояние освобождается. Для каждого указателя на (часть) состояния модуля необходимо удерживать ссылку на объект модуля.389390Обычно это не проблема, потому что типы, созданные с помощью [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.11/c-api/type.html#c.PyType_FromModuleAndSpec), и их экземпляры хранят ссылку на модуль. Однако нужно быть осторожным с подсчётом ссылок, когда вы ссылаетесь на состояние модуля из других мест, например, из колбэков внешних библиотек.391392## Открытые вопросы393394Несколько вопросов, связанных с состоянием на модуль и кучевыми типами, всё ещё открыты.395396Обсуждения по улучшению ситуации лучше всего вести в [capi-sig списке рассылки](https://python-all.ru/3.11/howto/isolating-extensions.html).397398### Область видимости для класса399400На данный момент (начиная с Python 3.11) невозможно привязать состояние к отдельным *типам*, не полагаясь на детали реализации CPython (которые могут измениться в будущем – возможно, по иронии судьбы, чтобы обеспечить правильное решение для области видимости на класс).401402### Преобразование в кучевые типы без потерь403404API кучевых типов не был разработан для «без потерь» преобразования из статических типов; то есть для создания типа, который работает точно так же, как заданный статический тип.405