isolating-extensions.md
1> **Источник:** https://python-all.ru/3.16/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.16/c-api/index.html#c-api-index), которые хотят сделать это расширение более безопасным в приложениях, где сам Python используется как библиотека.1819## Предыстория2021*Интерпретатор* – это среда выполнения кода Python. Он содержит конфигурацию (например, путь импорта) и состояние времени выполнения (например, набор импортированных модулей).2223Python поддерживает запуск нескольких интерпретаторов в одном процессе. Рассмотрим два случая: пользователи могут запускать интерпретаторы2425- последовательно, в несколько [`Py_InitializeEx()`](https://python-all.ru/3.16/c-api/interp-lifecycle.html#c.Py_InitializeEx)/[`Py_FinalizeEx()`](https://python-all.ru/3.16/c-api/interp-lifecycle.html#c.Py_FinalizeEx) циклов, и26- параллельно, управляя «субинтерпретаторами» с помощью [`Py_NewInterpreter()`](https://python-all.ru/3.16/c-api/subinterpreters.html#c.Py_NewInterpreter)/[`Py_EndInterpreter()`](https://python-all.ru/3.16/c-api/subinterpreters.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.16/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.16/howto/isolating-extensions.html#managing-global-state)), но они потребуют больше размышлений и внимания к краевым случаям.5758Хотя некоторые модули могли бы обойтись менее строгими ограничениями, изолированные модули упрощают установление чётких ожиданий и правил, работающих для множества вариантов использования.5960### Неожиданные краевые случаи6162Заметьте, что изолированные модули действительно создают некоторые неожиданные краевые случаи. В частности, каждый объект модуля обычно не разделяет свои классы и исключения с другими аналогичными модулями. Продолжая [пример выше](https://python-all.ru/3.16/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.16/howto/isolating-extensions.html#isolating-extensions-optout).9394### Управление состоянием на модуль9596Чтобы использовать состояние на модуль, используйте [многофазную инициализацию расширения](https://python-all.ru/3.16/c-api/extension-modules.html#multi-phase-initialization). Это сигнализирует, что ваш модуль корректно поддерживает несколько интерпретаторов.9798Установите `PyModuleDef.m_size` в положительное число, чтобы запросить соответствующее количество байт локального хранилища для модуля. Обычно это устанавливается в размер некоторой `struct`, специфичной для модуля, которая может хранить всё C-уровневое состояние модуля. В частности, сюда следует помещать указатели на классы (включая исключения, но исключая статические типы) и настройки (например, `csv`’s [`field_size_limit`](https://python-all.ru/3.16/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.16/howto/isolating-extensions.html); пример инициализации модуля приведён в нижней части файла.109110### Отказ: ограничение одним объектом модуля на процесс111112Неотрицательное значение `PyModuleDef.m_size` указывает, что модуль корректно поддерживает несколько интерпретаторов. Если ваш модуль этого ещё не делает, вы можете явно сделать его загружаемым только один раз на процесс. Например:113114```c115// Флаг уровня процесса116static int loaded = 0;117118// Мьютекс для обеспечения потокобезопасности (требуется только для free-threaded Python)119static PyMutex modinit_mutex = {0};120121static int122exec_module(PyObject* module)123{124 PyMutex_Lock(&modinit_mutex);125 if (loaded) {126 PyMutex_Unlock(&modinit_mutex);127 PyErr_SetString(PyExc_ImportError,128 "cannot load module more than once per process");129 return -1;130 }131 loaded = 1;132 PyMutex_Unlock(&modinit_mutex);133 // остальная часть инициализации134}135```136137Если функция [`PyModuleDef.m_clear`](https://python-all.ru/3.16/c-api/module.html#c.PyModuleDef.m_clear) вашего модуля способна подготовиться к будущей повторной инициализации, она должна сбросить флаг `loaded`. В этом случае ваш модуль не будет поддерживать одновременное существование нескольких экземпляров *одновременно*, но он будет, например, поддерживать загрузку после завершения работы среды выполнения Python ([`Py_FinalizeEx()`](https://python-all.ru/3.16/c-api/interp-lifecycle.html#c.Py_FinalizeEx)) и повторную инициализацию ([`Py_Initialize()`](https://python-all.ru/3.16/c-api/interp-lifecycle.html#c.Py_Initialize)).138139### Доступ к состоянию модуля из функций140141Доступ к состоянию из функций уровня модуля не представляет сложности. Функции получают объект модуля в качестве первого аргумента; для извлечения состояния можно использовать `PyModule_GetState`:142143```c144static PyObject *145func(PyObject *module, PyObject *args)146{147 my_struct *state = (my_struct*)PyModule_GetState(module);148 if (state == NULL) {149 return NULL;150 }151 // остальная часть логики152}153```154155> **Примечание**156>157> `PyModule_GetState` может вернуть `NULL` без установки исключения, если состояния модуля нет, т.е. `PyModuleDef.m_size` был нулевым. В вашем модуле управление `m_size` принадлежит вам, так что это легко предотвратить.158159## Кучные типы160161Традиционно типы, определённые в коде на C, являются *статическими*; то есть это структуры `static PyTypeObject`, определяемые непосредственно в коде и инициализируемые с помощью `PyType_Ready()`.162163Такие типы обязательно являются общими для всего процесса. Совместное использование их между объектами модулей требует внимания к любому состоянию, которым они владеют или к которому обращаются. Чтобы ограничить возможные проблемы, статические типы неизменяемы на уровне Python: например, нельзя задать `str.myattribute = 123`.164165**Особенность реализации CPython:** Совместное использование по-настоящему неизменяемых объектов между интерпретаторами не вызывает проблем, пока они не предоставляют доступа к изменяемым объектам. Однако в CPython каждый объект Python содержит изменяемую деталь реализации: счётчик ссылок. Изменения счётчика ссылок защищены GIL. Таким образом, код, разделяющий любые объекты Python между интерпретаторами, неявно зависит от текущего, общепроцессного GIL в CPython.166167Поскольку статические типы неизменяемы и глобальны для процесса, они не могут получить доступ к «своему» состоянию модуля. Если какой-либо метод такого типа требует доступа к состоянию модуля, тип необходимо преобразовать в *тип, размещаемый в куче*, или, сокращённо, *кучный тип*. Они больше соответствуют классам, создаваемым оператором `class` в Python.168169Для новых модулей использование кучных типов по умолчанию является хорошим эмпирическим правилом.170171### Преобразование статических типов в кучные типы172173Статические типы можно преобразовать в кучные, но учтите, что API кучных типов не был разработан для преобразования «без потерь» из статических типов – то есть для создания типа, работающего в точности как данный статический тип. Поэтому при переписывании определения класса в новом API вы, вероятно, непреднамеренно измените некоторые детали (например, возможность сериализации или унаследованные слоты). Всегда тестируйте детали, важные для вас.174175Обратите особое внимание на следующие два пункта (но учтите, что это не исчерпывающий список):176177- В отличие от статических типов, объекты кучных типов по умолчанию изменяемы. Используйте флаг [`Py_TPFLAGS_IMMUTABLETYPE`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_TPFLAGS_IMMUTABLETYPE), чтобы предотвратить изменяемость.178- Кучные типы по умолчанию наследуют [`tp_new`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_new), поэтому может стать возможным создание их экземпляров из кода Python. Вы можете предотвратить это с помощью флага [`Py_TPFLAGS_DISALLOW_INSTANTIATION`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_TPFLAGS_DISALLOW_INSTANTIATION).179180### Определение кучных типов181182Кучные типы можно создать, заполнив структуру [`PyType_Spec`](https://python-all.ru/3.16/c-api/type.html#c.PyType_Spec), которая является описанием или «чертежом» класса, и вызвав [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_FromModuleAndSpec) для создания нового объекта класса.183184> **Примечание**185>186> Другие функции, такие как [`PyType_FromSpec()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_FromSpec), также могут создавать кучные типы, но [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_FromModuleAndSpec) связывает модуль с классом, предоставляя доступ к состоянию модуля из методов.187188Класс обычно следует хранить *как в* состоянии модуля (для безопасного доступа из C), так и в `__dict__` модуля (для доступа из кода Python).189190### Протокол сборки мусора191192Экземпляры кучных типов содержат ссылку на свой тип. Это гарантирует, что тип не будет уничтожен до того, как будут уничтожены все его экземпляры, но может привести к циклам ссылок, которые должен разорвать сборщик мусора.193194Чтобы избежать утечек памяти, экземпляры кучных типов должны реализовывать протокол сборки мусора. А именно, кучные типы должны:195196- Иметь флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC).197- Определить функцию обхода с помощью [`Py_tp_traverse`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_tp_traverse), которая посещает тип (например, с помощью `Py_VISIT(Py_TYPE(self))`).198199Обратитесь к документации [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.16/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC) и [`tp_traverse`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_traverse) для дополнительных сведений.200201API для определения кучных типов развивался органически, что сделало его несколько неудобным в использовании в текущем виде. Следующие разделы помогут вам разобраться с типичными проблемами.202203#### `tp_traverse` в Python 3.8 и ниже204205Требование посещать тип из `tp_traverse` было добавлено в Python 3.9. Если вы поддерживаете Python 3.8 и ниже, функция обхода *не должна* посещать тип, поэтому она должна быть более сложной:206207```c208static int my_traverse(PyObject *self, visitproc visit, void *arg)209{210 if (Py_Version >= 0x03090000) {211 Py_VISIT(Py_TYPE(self));212 }213 return 0;214}215```216217К сожалению, [`Py_Version`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.Py_Version) был добавлен только в Python 3.11. В качестве замены используйте:218219- [`PY_VERSION_HEX`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.PY_VERSION_HEX), если не используется стабильный ABI, или220- [`sys.version_info`](https://python-all.ru/3.16/library/sys.html#sys.version_info) (через [`PySys_GetObject()`](https://python-all.ru/3.16/c-api/sys.html#c.PySys_GetObject) и [`PyArg_ParseTuple()`](https://python-all.ru/3.16/c-api/arg.html#c.PyArg_ParseTuple)).221222#### Делегирование `tp_traverse`223224Если функция обхода делегирует [`tp_traverse`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_traverse) своего базового класса (или другого типа), убедитесь, что `Py_TYPE(self)` посещается только один раз. Обратите внимание, что только тип кучи должен посещать тип в `tp_traverse`.225226Например, если ваша функция обхода включает:227228```c229base->tp_traverse(self, visit, arg)230```231232…и `base` может быть статическим типом, тогда она также должна включать:233234```c235if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {236 // tp_traverse типа‑кучи уже посетил Py_TYPE(self)237} else {238 if (Py_Version >= 0x03090000) {239 Py_VISIT(Py_TYPE(self));240 }241}242```243244Нет необходимости обрабатывать счетчик ссылок типа в [`tp_new`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_clear`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_clear).245246#### Определение `tp_dealloc`247248Если ваш тип имеет пользовательскую функцию [`tp_dealloc`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), она должна:249250- вызывать [`PyObject_GC_UnTrack()`](https://python-all.ru/3.16/c-api/gcsupport.html#c.PyObject_GC_UnTrack) до того, как будут инвалидированы любые поля, и251- уменьшить счетчик ссылок типа.252253Чтобы тип оставался валидным во время вызова `tp_free`, счетчик ссылок типа должен быть уменьшен *после* освобождения экземпляра. Например:254255```c256static void my_dealloc(PyObject *self)257{258 PyObject_GC_UnTrack(self);259 ...260 PyTypeObject *type = Py_TYPE(self);261 type->tp_free(self);262 Py_DECREF(type);263}264```265266Функция `tp_dealloc` по умолчанию делает это, поэтому если ваш тип *не* переопределяет `tp_dealloc`, вы можете не добавлять её.267268#### Не переопределение `tp_free`269270Слот [`tp_free`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_free) типа кучи должен быть установлен в [`PyObject_GC_Del()`](https://python-all.ru/3.16/c-api/gcsupport.html#c.PyObject_GC_Del). Это значение по умолчанию; не переопределяйте его.271272#### Избегание `PyObject_New`273274Объекты, отслеживаемые сборщиком мусора, должны выделяться с помощью функций, учитывающих сборщик мусора.275276Если вы используете [`PyObject_New()`](https://python-all.ru/3.16/c-api/allocation.html#c.PyObject_New) или [`PyObject_NewVar()`](https://python-all.ru/3.16/c-api/allocation.html#c.PyObject_NewVar):277278- Получить и вызвать слот [`tp_alloc`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_alloc) типа, если возможно. То есть заменить `TYPE *o = PyObject_New(TYPE, typeobj)` на:279280 ```c281 TYPE *o = typeobj->tp_alloc(typeobj, 0);282 ```283284 Заменить `o = PyObject_NewVar(TYPE, typeobj, size)` на то же самое, но использовать size вместо 0.285- Если вышеуказанное невозможно (например, внутри пользовательского `tp_alloc`), вызовите [`PyObject_GC_New()`](https://python-all.ru/3.16/c-api/gcsupport.html#c.PyObject_GC_New) или [`PyObject_GC_NewVar()`](https://python-all.ru/3.16/c-api/gcsupport.html#c.PyObject_GC_NewVar):286287 ```c288 TYPE *o = PyObject_GC_New(TYPE, typeobj);289290 TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, size);291 ```292293### Доступ к состоянию модуля из классов294295Если у вас есть объект типа, определенный с [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_FromModuleAndSpec), вы можете вызвать [`PyType_GetModule()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModule), чтобы получить связанный модуль, а затем [`PyModule_GetState()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_GetState), чтобы получить состояние модуля.296297Чтобы избавиться от утомительного шаблонного кода обработки ошибок, вы можете объединить эти два шага с помощью [`PyType_GetModuleState()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModuleState), получив:298299```c300my_struct *state = (my_struct*)PyType_GetModuleState(type);301if (state == NULL) {302 return NULL;303}304```305306### Доступ к состоянию модуля из обычных методов307308Доступ к состоянию на уровне модуля из методов класса несколько сложнее, но возможен благодаря API, введенному в Python 3.9. Чтобы получить состояние, сначала нужно получить *определяющий класс*, а затем получить из него состояние модуля.309310Самое большое препятствие – получение *класса, в котором был определен метод*, или, сокращенно, «определяющего класса» этого метода. Определяющий класс может содержать ссылку на модуль, частью которого он является.311312Не путайте определяющий класс с `Py_TYPE(self)`. Если метод вызывается для *подкласса* вашего типа, `Py_TYPE(self)` будет ссылаться на этот подкласс, который может быть определен в другом модуле, отличном от вашего.313314> **Примечание**315>316> Следующий код на Python может проиллюстрировать концепцию. `Base.get_defining_class` возвращает `Base`, даже если `type(self) == Sub`:317>318> ```python319> class Base:320> def get_type_of_self(self):321> return type(self)322>323> def get_defining_class(self):324> return __class__325>326> class Sub(Base):327> pass328> ```329330Чтобы метод получил свой «определяющий класс», он должен использовать [METH\_METHOD | METH\_FASTCALL | METH\_KEYWORDS](https://python-all.ru/3.16/c-api/structures.html#meth-method-meth-fastcall-meth-keywords) [`calling convention`](https://python-all.ru/3.16/c-api/structures.html#c.PyMethodDef) и соответствующую сигнатуру [`PyCMethod`](https://python-all.ru/3.16/c-api/structures.html#c.PyCMethod):331332```c333PyObject *PyCMethod(334 PyObject *self, // объект, на котором вызван метод335 PyTypeObject *defining_class, // определяющий класс336 PyObject *const *args, // C‑массив аргументов337 Py_ssize_t nargs, // длина "args"338 PyObject *kwnames) // NULL или словарь именованных аргументов339```340341Получив определяющий класс, вызовите [`PyType_GetModuleState()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModuleState), чтобы получить состояние связанного с ним модуля.342343Например:344345```c346static PyObject *347example_method(PyObject *self,348 PyTypeObject *defining_class,349 PyObject *const *args,350 Py_ssize_t nargs,351 PyObject *kwnames)352{353 my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);354 if (state == NULL) {355 return NULL;356 }357 ... // остальная логика358}359360PyDoc_STRVAR(example_method_doc, "...");361362static PyMethodDef my_methods[] = {363 {"example_method",364 (PyCFunction)(void(*)(void))example_method,365 METH_METHOD|METH_FASTCALL|METH_KEYWORDS,366 example_method_doc}367 {NULL},368}369```370371### Доступ к состоянию модуля из слотовых методов, геттеров и сеттеров372373> **Примечание**374>375> Это новое в Python 3.11.376377Слот-методы – быстрые C-эквиваленты специальных методов, такие как [`nb_add`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyNumberMethods.nb_add) для [`__add__`](https://python-all.ru/3.16/reference/datamodel.html#object.__add__) или [`tp_new`](https://python-all.ru/3.16/c-api/typeobj.html#c.PyTypeObject.tp_new) для инициализации – имеют очень простой API, который не позволяет передавать определяющий класс, в отличие от [`PyCMethod`](https://python-all.ru/3.16/c-api/structures.html#c.PyCMethod). То же самое относится к геттерам и сеттерам, определённым с помощью [`PyGetSetDef`](https://python-all.ru/3.16/c-api/structures.html#c.PyGetSetDef).378379Чтобы получить состояние модуля в таких случаях, используйте функцию [`PyType_GetModuleByDef()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_GetModuleByDef) и передайте определение модуля. Получив модуль, вызовите [`PyModule_GetState()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_GetState), чтобы получить состояние:380381```c382PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);383my_struct *state = (my_struct*)PyModule_GetState(module);384if (state == NULL) {385 return NULL;386}387```388389`PyType_GetModuleByDef()` работает путём поиска в [порядке разрешения методов](https://python-all.ru/3.16/glossary.html#term-method-resolution-order) (то есть во всех суперклассах) первого суперкласса, у которого есть соответствующий модуль.390391> **Примечание**392>393> В очень необычных случаях (цепочки наследования, охватывающие несколько модулей, созданных из одного определения) `PyType_GetModuleByDef()` может не вернуть модуль истинного определяющего класса. Однако он всегда будет возвращать модуль с тем же определением, что гарантирует совместимую структуру памяти C.394395### Время жизни состояния модуля396397Когда объект модуля собирается сборщиком мусора, его состояние освобождается. Для каждого указателя на (часть) состояния модуля необходимо удерживать ссылку на объект модуля.398399Обычно это не проблема, потому что типы, созданные с помощью [`PyType_FromModuleAndSpec()`](https://python-all.ru/3.16/c-api/type.html#c.PyType_FromModuleAndSpec), и их экземпляры хранят ссылку на модуль. Однако нужно быть осторожным с подсчётом ссылок, когда вы ссылаетесь на состояние модуля из других мест, например, из колбэков внешних библиотек.400401## Открытые вопросы402403Несколько вопросов, связанных с состоянием на модуль и кучевыми типами, всё ещё открыты.404405Обсуждения по улучшению ситуации лучше всего вести на [форуме discuss под тегом c-api](https://python-all.ru/3.16/howto/isolating-extensions.html).406407### Область видимости для класса408409На данный момент (начиная с Python 3.11) невозможно привязать состояние к отдельным *типам*, не полагаясь на детали реализации CPython (которые могут измениться в будущем – возможно, по иронии судьбы, чтобы обеспечить правильное решение для области видимости на класс).410411### Преобразование в кучевые типы без потерь412413API кучевых типов не был разработан для «без потерь» преобразования из статических типов; то есть для создания типа, который работает точно так же, как заданный статический тип.414