newtypes_tutorial.md
1> **Источник:** https://python-all.ru/3.13/extending/newtypes_tutorial.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 2. Определение типов расширений: учебное пособие89Python позволяет разработчику модуля расширения на C определять новые типы, которыми можно манипулировать из кода Python, во многом как встроенные типы [`str`](https://python-all.ru/3.13/library/stdtypes.html#str) и [`list`](https://python-all.ru/3.13/library/stdtypes.html#list). Код для всех типов расширений следует определённому шаблону, но есть некоторые детали, которые необходимо понять, прежде чем приступить к работе. Этот документ является вводным руководством по данной теме.1011## 2.1. Основы1213Среда выполнения [CPython](https://python-all.ru/3.13/glossary.html#term-CPython) видит все объекты Python как переменные типа [PyObject](https://python-all.ru/3.13/c-api/structures.html#c.PyObject)\*, который служит «базовым типом» для всех объектов Python. Сама структура [`PyObject`](https://python-all.ru/3.13/c-api/structures.html#c.PyObject) содержит только [счётчик ссылок](https://python-all.ru/3.13/glossary.html#term-reference-count) объекта и указатель на его «объект типа». В этом и заключается суть: объект типа определяет, какие (C) функции вызывает интерпретатор, когда, например, выполняется поиск атрибута у объекта, вызов метода или умножение на другой объект. Эти C-функции называются «методами типа».1415Итак, чтобы определить новый тип расширения, необходимо создать новый объект типа.1617Такие вещи можно объяснить только на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип с именем `Custom` внутри C-модуля расширения `custom`:1819> **Примечание**20>21> То, что мы показываем здесь, – это традиционный способ определения *статических* типов расширений. Он должен быть достаточным для большинства случаев. C API также позволяет определять типы расширений, размещаемые в куче, с помощью функции [`PyType_FromSpec()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_FromSpec), которая не рассматривается в этом руководстве.2223```c24#define PY_SSIZE_T_CLEAN25#include <Python.h>2627typedef struct {28 PyObject_HEAD29 /* Сюда помещаются поля, специфичные для типа. */30} CustomObject;3132static PyTypeObject CustomType = {33 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)34 .tp_name = "custom.Custom",35 .tp_doc = PyDoc_STR("Custom objects"),36 .tp_basicsize = sizeof(CustomObject),37 .tp_itemsize = 0,38 .tp_flags = Py_TPFLAGS_DEFAULT,39 .tp_new = PyType_GenericNew,40};4142static int43custom_module_exec(PyObject *m)44{45 if (PyType_Ready(&CustomType) < 0) {46 return -1;47 }4849 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {50 return -1;51 }5253 return 0;54}5556static PyModuleDef_Slot custom_module_slots[] = {57 {Py_mod_exec, custom_module_exec},58 // Просто используйте это при работе со статическими типами59 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},60 {0, NULL}61};6263static PyModuleDef custom_module = {64 .m_base = PyModuleDef_HEAD_INIT,65 .m_name = "custom",66 .m_doc = "Example module that creates an extension type.",67 .m_size = 0,68 .m_slots = custom_module_slots,69};7071PyMODINIT_FUNC72PyInit_custom(void)73{74 return PyModuleDef_Init(&custom_module);75}76```7778Это довольно много для одномоментного восприятия, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы. Этот файл определяет три вещи:79801. Что содержит `Custom` **объект**: это структура `CustomObject`, которая выделяется один раз для каждого экземпляра `Custom`.812. Как ведёт себя `Custom` **тип**: это структура `CustomType`, которая определяет набор флагов и указателей на функции, которые интерпретатор проверяет при запросе определённых операций.823. Как определить и выполнить модуль `custom`: это функция `PyInit_custom` и связанная с ней структура `custom_module` для определения модуля, а также функция `custom_module_exec` для настройки нового объекта модуля.8384Первая часть:8586```c87typedef struct {88 PyObject_HEAD89} CustomObject;90```9192Вот что будет содержать объект Custom. `PyObject_HEAD` является обязательным в начале каждой структуры объекта и определяет поле с именем `ob_base` типа [`PyObject`](https://python-all.ru/3.13/c-api/structures.html#c.PyObject), содержащее указатель на объект типа и счётчик ссылок (к ним можно получить доступ с помощью макросов [`Py_TYPE`](https://python-all.ru/3.13/c-api/structures.html#c.Py_TYPE) и [`Py_REFCNT`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_REFCNT) соответственно). Смысл макроса – абстрагироваться от расположения полей и разрешить дополнительные поля в [отладочных сборках](https://python-all.ru/3.13/using/configure.html#debug-build).9394> **Примечание**95>96> В приведённом выше коде нет точки с запятой после макроса [`PyObject_HEAD`](https://python-all.ru/3.13/c-api/structures.html#c.PyObject_HEAD). Будьте осторожны, чтобы случайно её не добавить: некоторые компиляторы выдадут ошибку.9798Конечно, объекты обычно хранят дополнительные данные помимо стандартного шаблона `PyObject_HEAD`; например, вот определение для стандартных чисел с плавающей запятой Python:99100```c101typedef struct {102 PyObject_HEAD103 double ob_fval;104} PyFloatObject;105```106107Вторая часть – это определение объекта типа.108109```c110static PyTypeObject CustomType = {111 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)112 .tp_name = "custom.Custom",113 .tp_doc = PyDoc_STR("Custom objects"),114 .tp_basicsize = sizeof(CustomObject),115 .tp_itemsize = 0,116 .tp_flags = Py_TPFLAGS_DEFAULT,117 .tp_new = PyType_GenericNew,118};119```120121> **Примечание**122>123> Рекомендуется использовать именованные инициализаторы в стиле C99, как указано выше, чтобы не перечислять все поля [`PyTypeObject`](https://python-all.ru/3.13/c-api/type.html#c.PyTypeObject), которые не важны, а также не беспокоиться о порядке объявления полей.124125Фактическое определение [`PyTypeObject`](https://python-all.ru/3.13/c-api/type.html#c.PyTypeObject) в `object.h` содержит гораздо больше [полей](https://python-all.ru/3.13/c-api/typeobj.html#type-structs), чем приведённое выше определение. Оставшиеся поля будут заполнены нулями компилятором C, и обычно принято не указывать их явно, если они не нужны.126127Мы разберём его, поле за полем:128129```c130.ob_base = PyVarObject_HEAD_INIT(NULL, 0)131```132133Эта строка является обязательным шаблоном для инициализации поля `ob_base`, упомянутого выше.134135```c136.tp_name = "custom.Custom",137```138139Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:140141```pycon142>>> "" + custom.Custom()143Traceback (most recent call last):144 File "<stdin>", line 1, in <module>145TypeError: can only concatenate str (not "custom.Custom") to str146```147148Обратите внимание, что имя является составным (с точками) и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `custom`, а тип – `Custom`, поэтому мы устанавливаем имя типа `custom.Custom`. Использование реального составного пути импорта важно для совместимости вашего типа с модулями [`pydoc`](https://python-all.ru/3.13/library/pydoc.html#module-pydoc) и [`pickle`](https://python-all.ru/3.13/library/pickle.html#module-pickle).149150```c151.tp_basicsize = sizeof(CustomObject),152.tp_itemsize = 0,153```154155Это нужно, чтобы Python знал, сколько памяти выделять при создании новых экземпляров `Custom`. [`tp_itemsize`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_itemsize) используется только для объектов переменного размера, в остальных случаях должно быть равно нулю.156157> **Примечание**158>159> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.13/reference/datamodel.html#type.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для `tp_basicsize` вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.13/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.160161Мы устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_DEFAULT).162163```c164.tp_flags = Py_TPFLAGS_DEFAULT,165```166167Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.168169Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_doc).170171```c172.tp_doc = PyDoc_STR("Custom objects"),173```174175Чтобы разрешить создание объектов, мы должны предоставить обработчик [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new). Это эквивалент метода Python [`__new__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__new__), но должен быть указан явно. В данном случае мы можем просто использовать реализацию по умолчанию, предоставляемую API-функцией [`PyType_GenericNew()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GenericNew).176177```c178.tp_new = PyType_GenericNew,179```180181Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `custom_module_exec()`:182183```c184if (PyType_Ready(&CustomType) < 0) {185 return -1;186}187```188189Это инициализирует тип `Custom`, заполняя ряд членов подходящими значениями по умолчанию, включая [`ob_type`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyObject.ob_type), который мы изначально установили в `NULL`.190191```c192if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {193 return -1;194}195```196197Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Custom` вызовом класса `Custom`:198199```pycon200>>> import custom201>>> mycustom = custom.Custom()202```203204Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `custom.c`,205206```c207[build-system]208requires = ["setuptools"]209build-backend = "setuptools.build_meta"210211[project]212name = "custom"213version = "1"214```215216в файл с именем `pyproject.toml`, и217218```python219from setuptools import Extension, setup220setup(ext_modules=[Extension("custom", ["custom.c"])])221```222223в файл с именем `setup.py`; затем введите224225```console226$ python -m pip install .227```228229в оболочке должен создать файл `custom.so` в подкаталоге и установить его; теперь запустите Python – вы сможете `import custom` и поиграть с объектами `Custom`.230231Было не так сложно, правда?232233Конечно, текущий тип Custom довольно неинтересен. У него нет данных, и он ничего не делает. Его даже нельзя унаследовать.234235## 2.2. Добавление данных и методов к базовому примеру236237Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `custom2`, который добавляет эти возможности:238239```c240#define PY_SSIZE_T_CLEAN241#include <Python.h>242#include <stddef.h> /* для offsetof() */243244typedef struct {245 PyObject_HEAD246 PyObject *first; /* имя */247 PyObject *last; /* фамилия */248 int number;249} CustomObject;250251static void252Custom_dealloc(CustomObject *self)253{254 Py_XDECREF(self->first);255 Py_XDECREF(self->last);256 Py_TYPE(self)->tp_free((PyObject *) self);257}258259static PyObject *260Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)261{262 CustomObject *self;263 self = (CustomObject *) type->tp_alloc(type, 0);264 if (self != NULL) {265 self->first = PyUnicode_FromString("");266 if (self->first == NULL) {267 Py_DECREF(self);268 return NULL;269 }270 self->last = PyUnicode_FromString("");271 if (self->last == NULL) {272 Py_DECREF(self);273 return NULL;274 }275 self->number = 0;276 }277 return (PyObject *) self;278}279280static int281Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)282{283 static char *kwlist[] = {"first", "last", "number", NULL};284 PyObject *first = NULL, *last = NULL;285286 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,287 &first, &last,288 &self->number))289 return -1;290291 if (first) {292 Py_XSETREF(self->first, Py_NewRef(first));293 }294 if (last) {295 Py_XSETREF(self->last, Py_NewRef(last));296 }297 return 0;298}299300static PyMemberDef Custom_members[] = {301 {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,302 "first name"},303 {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,304 "last name"},305 {"number", Py_T_INT, offsetof(CustomObject, number), 0,306 "custom number"},307 {NULL} /* Страж */308};309310static PyObject *311Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))312{313 if (self->first == NULL) {314 PyErr_SetString(PyExc_AttributeError, "first");315 return NULL;316 }317 if (self->last == NULL) {318 PyErr_SetString(PyExc_AttributeError, "last");319 return NULL;320 }321 return PyUnicode_FromFormat("%S %S", self->first, self->last);322}323324static PyMethodDef Custom_methods[] = {325 {"name", (PyCFunction) Custom_name, METH_NOARGS,326 "Return the name, combining the first and last name"327 },328 {NULL} /* Страж */329};330331static PyTypeObject CustomType = {332 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)333 .tp_name = "custom2.Custom",334 .tp_doc = PyDoc_STR("Custom objects"),335 .tp_basicsize = sizeof(CustomObject),336 .tp_itemsize = 0,337 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,338 .tp_new = Custom_new,339 .tp_init = (initproc) Custom_init,340 .tp_dealloc = (destructor) Custom_dealloc,341 .tp_members = Custom_members,342 .tp_methods = Custom_methods,343};344345static int346custom_module_exec(PyObject *m)347{348 if (PyType_Ready(&CustomType) < 0) {349 return -1;350 }351352 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {353 return -1;354 }355356 return 0;357}358359static PyModuleDef_Slot custom_module_slots[] = {360 {Py_mod_exec, custom_module_exec},361 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},362 {0, NULL}363};364365static PyModuleDef custom_module = {366 .m_base = PyModuleDef_HEAD_INIT,367 .m_name = "custom2",368 .m_doc = "Example module that creates an extension type.",369 .m_size = 0,370 .m_slots = custom_module_slots,371};372373PyMODINIT_FUNC374PyInit_custom2(void)375{376 return PyModuleDef_Init(&custom_module);377}378```379380Эта версия модуля содержит ряд изменений.381382Тип `Custom` теперь имеет три атрибута данных в своей C-структуре: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – это целое число C.383384Структура объекта обновляется соответствующим образом:385386```c387typedef struct {388 PyObject_HEAD389 PyObject *first; /* имя */390 PyObject *last; /* фамилия */391 int number;392} CustomObject;393```394395Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:396397```c398static void399Custom_dealloc(CustomObject *self)400{401 Py_XDECREF(self->first);402 Py_XDECREF(self->last);403 Py_TYPE(self)->tp_free((PyObject *) self);404}405```406407который присваивается члену [`tp_dealloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):408409```c410.tp_dealloc = (destructor) Custom_dealloc,411```412413Этот метод сначала сбрасывает счётчики ссылок двух атрибутов Python. [`Py_XDECREF()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_XDECREF) корректно обрабатывает случай, когда его аргумент равен `NULL` (что может произойти, если `tp_new` не выполнился до конца). Затем он вызывает член [`tp_free`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта (вычисленный с помощью `Py_TYPE(self)`) для освобождения памяти объекта. Обратите внимание, что тип объекта может не быть `CustomType`, поскольку объект может быть экземпляром подкласса.414415> **Примечание**416>417> Явное приведение к `destructor` выше необходимо, потому что мы определили `Custom_dealloc` как принимающий аргумент `CustomObject *`, но указатель на функцию `tp_dealloc` ожидает получения аргумента `PyObject *`. В противном случае компилятор выдаст предупреждение. Это объектно-ориентированный полиморфизм в C!418419Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому мы предоставляем реализацию `tp_new`:420421```c422static PyObject *423Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)424{425 CustomObject *self;426 self = (CustomObject *) type->tp_alloc(type, 0);427 if (self != NULL) {428 self->first = PyUnicode_FromString("");429 if (self->first == NULL) {430 Py_DECREF(self);431 return NULL;432 }433 self->last = PyUnicode_FromString("");434 if (self->last == NULL) {435 Py_DECREF(self);436 return NULL;437 }438 self->number = 0;439 }440 return (PyObject *) self;441}442```443444и устанавливаем его в член [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new):445446```c447.tp_new = Custom_new,448```449450Обработчик `tp_new` отвечает за создание (в отличие от инициализации) объектов типа. Он предоставляется в Python как метод [`__new__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__new__). Не требуется определять член `tp_new`, и действительно, многие расширения типов просто повторно используют [`PyType_GenericNew()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GenericNew), как это сделано в первой версии типа `Custom` выше. В этом случае мы используем обработчик `tp_new` для инициализации атрибутов `first` и `last` значениями по умолчанию, отличными от `NULL`.451452`tp_new` передаётся тип, экземпляр которого создаётся (не обязательно `CustomType`, если создаётся экземпляр подкласса), и любые аргументы, переданные при вызове типа; ожидается, что он вернёт созданный экземпляр. Обработчики `tp_new` всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам (также известным как `tp_init` в C или `__init__` в Python).453454> **Примечание**455>456> `tp_new` не должен вызывать `tp_init` явно, так как интерпретатор сделает это сам.457458Реализация `tp_new` вызывает слот [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти:459460```c461self = (CustomObject *) type->tp_alloc(type, 0);462```463464Поскольку выделение памяти может завершиться неудачей, мы должны проверить результат [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc) на `NULL` перед продолжением.465466> **Примечание**467>468> Мы не заполняли слот [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.13/library/functions.html#object). Большинство типов используют стратегию выделения по умолчанию.469470> **Примечание**471>472> При создании кооперативного [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new) (такого, который вызывает `tp_new` или [`__new__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__new__) базового типа) нельзя *не* пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте тип, который будете вызывать, и вызывайте его `tp_new` напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуются от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.13/library/exceptions.html#TypeError).)473474Мы также определяем функцию инициализации, которая принимает аргументы для предоставления начальных значений экземпляру:475476```c477static int478Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)479{480 static char *kwlist[] = {"first", "last", "number", NULL};481 PyObject *first = NULL, *last = NULL, *tmp;482483 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,484 &first, &last,485 &self->number))486 return -1;487488 if (first) {489 tmp = self->first;490 Py_INCREF(first);491 self->first = first;492 Py_XDECREF(tmp);493 }494 if (last) {495 tmp = self->last;496 Py_INCREF(last);497 self->last = last;498 Py_XDECREF(tmp);499 }500 return 0;501}502```503504заполняя слот [`tp_init`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_init).505506```c507.tp_init = (initproc) Custom_init,508```509510Слот [`tp_init`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_init) открыт в Python как метод [`__init__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. Инициализаторы всегда принимают позиционные и именованные аргументы и должны возвращать либо `0` в случае успеха, либо `-1` в случае ошибки.511512В отличие от обработчика `tp_new`, нет никакой гарантии, что `tp_init` будет вызван (например, модуль [`pickle`](https://python-all.ru/3.13/library/pickle.html#module-pickle) по умолчанию не вызывает [`__init__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__init__) для распакованных экземпляров). Он также может быть вызван несколько раз. Кто угодно может вызвать метод `__init__()` на наших объектах. По этой причине нужно быть особенно осторожным при присвоении новых значений атрибутов. Может возникнуть соблазн, например, присвоить члену `first` такое значение:513514```c515if (first) {516 Py_XDECREF(self->first);517 Py_INCREF(first);518 self->first = first;519}520```521522But this would be risky. Our type doesn’t restrict the type of the `first` member, so it could be any kind of object. It could have a destructor that causes code to be executed that tries to access the `first` member; or that destructor could release the [Global interpreter Lock](https://python-all.ru/3.13/glossary.html#term-GIL) and let arbitrary code run in other threads that accesses and modifies our object.523524Чтобы быть параноидальными и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?525526- когда мы точно знаем, что счётчик ссылок больше 1;527- when we know that deallocation of the object [\[1\]](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id5) will neither release the [GIL](https://python-all.ru/3.13/glossary.html#term-GIL) nor cause any calls back into our type’s code;528- когда уменьшается счётчик ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) для типа, который не поддерживает циклическую сборку мусора [\[2\]](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id6).529530Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:531532```c533static PyMemberDef Custom_members[] = {534 {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,535 "first name"},536 {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,537 "last name"},538 {"number", Py_T_INT, offsetof(CustomObject, number), 0,539 "custom number"},540 {NULL} /* Страж */541};542```543544и поместить определения в слот [`tp_members`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_members):545546```c547.tp_members = Custom_members,548```549550Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Общее управление атрибутами](https://python-all.ru/3.13/extending/newtypes.html#generic-attribute-management) ниже.551552Недостаток этого подхода в том, что он не даёт способа ограничить типы объектов, которые можно присваивать атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Кроме того, атрибуты можно удалить, установив указатели C в `NULL`. Даже если мы можем гарантировать, что члены инициализируются значениями, отличными от `NULL`, члены могут быть установлены в `NULL`, если атрибуты удалены.553554Мы определяем один метод `Custom.name()`, который выводит имя объекта как конкатенацию имени и фамилии.555556```c557static PyObject *558Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))559{560 if (self->first == NULL) {561 PyErr_SetString(PyExc_AttributeError, "first");562 return NULL;563 }564 if (self->last == NULL) {565 PyErr_SetString(PyExc_AttributeError, "last");566 return NULL;567 }568 return PyUnicode_FromFormat("%S %S", self->first, self->last);569}570```571572Метод реализован как функция на C, которая принимает экземпляр `Custom` (или подкласс `Custom`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:573574```python575def name(self):576 return "%s %s" % (self.first, self.last)577```578579Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны `NULL`. Это потому, что их можно удалить, и в этом случае они устанавливаются в `NULL`. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Как это сделать, мы увидим в следующем разделе.580581Теперь, когда мы определили метод, нужно создать массив определений методов:582583```c584static PyMethodDef Custom_methods[] = {585 {"name", (PyCFunction) Custom_name, METH_NOARGS,586 "Return the name, combining the first and last name"587 },588 {NULL} /* Страж */589};590```591592(обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.13/c-api/structures.html#c.METH_NOARGS), чтобы указать, что метод не ожидает аргументов, кроме *self*)593594и назначить его слоту [`tp_methods`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_methods):595596```c597.tp_methods = Custom_methods,598```599600Наконец, сделаем наш тип пригодным для использования в качестве базового класса для создания подклассов. Мы написали наши методы достаточно осторожно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_BASETYPE) в определение флага нашего класса:601602```c603.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,604```605606Мы переименовываем `PyInit_custom()` в `PyInit_custom2()`, обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.13/c-api/module.html#c.PyModuleDef) и обновляем полное имя класса в структуре [`PyTypeObject`](https://python-all.ru/3.13/c-api/type.html#c.PyTypeObject).607608Наконец, обновляем файл `setup.py`, чтобы включить новый модуль,609610```python611from setuptools import Extension, setup612setup(ext_modules=[613 Extension("custom", ["custom.c"]),614 Extension("custom2", ["custom2.c"]),615])616```617618а затем переустанавливаем, чтобы можно было `import custom2`:619620```console621$ python -m pip install .622```623624## 2.3. Предоставление более точного управления атрибутами данных625626В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Custom`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.627628```c629#define PY_SSIZE_T_CLEAN630#include <Python.h>631#include <stddef.h> /* для offsetof() */632633typedef struct {634 PyObject_HEAD635 PyObject *first; /* имя */636 PyObject *last; /* фамилия */637 int number;638} CustomObject;639640static void641Custom_dealloc(CustomObject *self)642{643 Py_XDECREF(self->first);644 Py_XDECREF(self->last);645 Py_TYPE(self)->tp_free((PyObject *) self);646}647648static PyObject *649Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)650{651 CustomObject *self;652 self = (CustomObject *) type->tp_alloc(type, 0);653 if (self != NULL) {654 self->first = PyUnicode_FromString("");655 if (self->first == NULL) {656 Py_DECREF(self);657 return NULL;658 }659 self->last = PyUnicode_FromString("");660 if (self->last == NULL) {661 Py_DECREF(self);662 return NULL;663 }664 self->number = 0;665 }666 return (PyObject *) self;667}668669static int670Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)671{672 static char *kwlist[] = {"first", "last", "number", NULL};673 PyObject *first = NULL, *last = NULL;674675 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,676 &first, &last,677 &self->number))678 return -1;679680 if (first) {681 Py_SETREF(self->first, Py_NewRef(first));682 }683 if (last) {684 Py_SETREF(self->last, Py_NewRef(last));685 }686 return 0;687}688689static PyMemberDef Custom_members[] = {690 {"number", Py_T_INT, offsetof(CustomObject, number), 0,691 "custom number"},692 {NULL} /* Страж */693};694695static PyObject *696Custom_getfirst(CustomObject *self, void *closure)697{698 return Py_NewRef(self->first);699}700701static int702Custom_setfirst(CustomObject *self, PyObject *value, void *closure)703{704 if (value == NULL) {705 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");706 return -1;707 }708 if (!PyUnicode_Check(value)) {709 PyErr_SetString(PyExc_TypeError,710 "The first attribute value must be a string");711 return -1;712 }713 Py_SETREF(self->first, Py_NewRef(value));714 return 0;715}716717static PyObject *718Custom_getlast(CustomObject *self, void *closure)719{720 return Py_NewRef(self->last);721}722723static int724Custom_setlast(CustomObject *self, PyObject *value, void *closure)725{726 if (value == NULL) {727 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");728 return -1;729 }730 if (!PyUnicode_Check(value)) {731 PyErr_SetString(PyExc_TypeError,732 "The last attribute value must be a string");733 return -1;734 }735 Py_SETREF(self->last, Py_NewRef(value));736 return 0;737}738739static PyGetSetDef Custom_getsetters[] = {740 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,741 "first name", NULL},742 {"last", (getter) Custom_getlast, (setter) Custom_setlast,743 "last name", NULL},744 {NULL} /* Страж */745};746747static PyObject *748Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))749{750 return PyUnicode_FromFormat("%S %S", self->first, self->last);751}752753static PyMethodDef Custom_methods[] = {754 {"name", (PyCFunction) Custom_name, METH_NOARGS,755 "Return the name, combining the first and last name"756 },757 {NULL} /* Страж */758};759760static PyTypeObject CustomType = {761 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)762 .tp_name = "custom3.Custom",763 .tp_doc = PyDoc_STR("Custom objects"),764 .tp_basicsize = sizeof(CustomObject),765 .tp_itemsize = 0,766 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,767 .tp_new = Custom_new,768 .tp_init = (initproc) Custom_init,769 .tp_dealloc = (destructor) Custom_dealloc,770 .tp_members = Custom_members,771 .tp_methods = Custom_methods,772 .tp_getset = Custom_getsetters,773};774775static int776custom_module_exec(PyObject *m)777{778 if (PyType_Ready(&CustomType) < 0) {779 return -1;780 }781782 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {783 return -1;784 }785786 return 0;787}788789static PyModuleDef_Slot custom_module_slots[] = {790 {Py_mod_exec, custom_module_exec},791 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},792 {0, NULL}793};794795static PyModuleDef custom_module = {796 .m_base = PyModuleDef_HEAD_INIT,797 .m_name = "custom3",798 .m_doc = "Example module that creates an extension type.",799 .m_size = 0,800 .m_slots = custom_module_slots,801};802803PyMODINIT_FUNC804PyInit_custom3(void)805{806 return PyModuleDef_Init(&custom_module);807}808```809810Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:811812```c813static PyObject *814Custom_getfirst(CustomObject *self, void *closure)815{816 Py_INCREF(self->first);817 return self->first;818}819820static int821Custom_setfirst(CustomObject *self, PyObject *value, void *closure)822{823 PyObject *tmp;824 if (value == NULL) {825 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");826 return -1;827 }828 if (!PyUnicode_Check(value)) {829 PyErr_SetString(PyExc_TypeError,830 "The first attribute value must be a string");831 return -1;832 }833 tmp = self->first;834 Py_INCREF(value);835 self->first = value;836 Py_DECREF(tmp);837 return 0;838}839```840841Функция получения принимает объект `Custom` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это может быть использовано, например, для создания единого набора функций получения и установки, которые решают, какой атрибут получить или установить на основе данных в замыкании.)842843Функция установки принимает объект `Custom`, новое значение и замыкание. Новое значение может быть `NULL`, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или его новое значение не является строкой.844845Мы создаём массив структур [`PyGetSetDef`](https://python-all.ru/3.13/c-api/structures.html#c.PyGetSetDef):846847```c848static PyGetSetDef Custom_getsetters[] = {849 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,850 "first name", NULL},851 {"last", (getter) Custom_getlast, (setter) Custom_setlast,852 "last name", NULL},853 {NULL} /* Страж */854};855```856857и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_getset):858859```c860.tp_getset = Custom_getsetters,861```862863Последний элемент структуры [`PyGetSetDef`](https://python-all.ru/3.13/c-api/structures.html#c.PyGetSetDef) – это «замыкание», упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём `NULL`.864865Также удаляем определения членов для этих атрибутов:866867```c868static PyMemberDef Custom_members[] = {869 {"number", Py_T_INT, offsetof(CustomObject, number), 0,870 "custom number"},871 {NULL} /* Страж */872};873```874875Также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id7):876877```c878static int879Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)880{881 static char *kwlist[] = {"first", "last", "number", NULL};882 PyObject *first = NULL, *last = NULL, *tmp;883884 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,885 &first, &last,886 &self->number))887 return -1;888889 if (first) {890 tmp = self->first;891 Py_INCREF(first);892 self->first = first;893 Py_DECREF(tmp);894 }895 if (last) {896 tmp = self->last;897 Py_INCREF(last);898 self->last = last;899 Py_DECREF(tmp);900 }901 return 0;902}903```904905Благодаря этим изменениям можно гарантировать, что члены `first` и `last` никогда не будут `NULL`, поэтому проверки на значения `NULL` можно убрать почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_DECREF). Единственное место, где эти вызовы нельзя изменить, – реализация `tp_dealloc`, где возможна ситуация, что инициализация этих членов завершилась неудачей в `tp_new`.906907Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.908909## 2.4. Поддержка циклической сборки мусора910911Python имеет [циклический сборщик мусора (GC)](https://python-all.ru/3.13/glossary.html#term-garbage-collection), который может определять ненужные объекты даже при ненулевом счётчике ссылок. Это возможно, когда объекты образуют циклы. Например, рассмотрим:912913```pycon914>>> l = []915>>> l.append(l)916>>> del l917```918919В этом примере создаётся список, содержащий сам себя. При его удалении у него всё ещё остаётся ссылка на самого себя. Счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.920921Во второй версии примера `Custom` мы разрешили хранить объекты любого типа в атрибутах `first` или `last` [\[4\]](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id8). Кроме того, во второй и третьей версиях мы разрешили наследование от `Custom`, а подклассы могут добавлять произвольные атрибуты. По любой из этих двух причин объекты `Custom` могут участвовать в циклах:922923```pycon924>>> import custom3925>>> class Derived(custom3.Custom): pass926...927>>> n = Derived()928>>> n.some_attribute = n929```930931Чтобы экземпляр `Custom`, участвующий в цикле ссылок, был корректно обнаружен и собран циклическим GC, наш тип `Custom` должен заполнить два дополнительных слота и включить флаг, активирующий эти слоты:932933```c934#define PY_SSIZE_T_CLEAN935#include <Python.h>936#include <stddef.h> /* для offsetof() */937938typedef struct {939 PyObject_HEAD940 PyObject *first; /* имя */941 PyObject *last; /* фамилия */942 int number;943} CustomObject;944945static int946Custom_traverse(CustomObject *self, visitproc visit, void *arg)947{948 Py_VISIT(self->first);949 Py_VISIT(self->last);950 return 0;951}952953static int954Custom_clear(CustomObject *self)955{956 Py_CLEAR(self->first);957 Py_CLEAR(self->last);958 return 0;959}960961static void962Custom_dealloc(CustomObject *self)963{964 PyObject_GC_UnTrack(self);965 Custom_clear(self);966 Py_TYPE(self)->tp_free((PyObject *) self);967}968969static PyObject *970Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)971{972 CustomObject *self;973 self = (CustomObject *) type->tp_alloc(type, 0);974 if (self != NULL) {975 self->first = PyUnicode_FromString("");976 if (self->first == NULL) {977 Py_DECREF(self);978 return NULL;979 }980 self->last = PyUnicode_FromString("");981 if (self->last == NULL) {982 Py_DECREF(self);983 return NULL;984 }985 self->number = 0;986 }987 return (PyObject *) self;988}989990static int991Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)992{993 static char *kwlist[] = {"first", "last", "number", NULL};994 PyObject *first = NULL, *last = NULL;995996 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,997 &first, &last,998 &self->number))999 return -1;10001001 if (first) {1002 Py_SETREF(self->first, Py_NewRef(first));1003 }1004 if (last) {1005 Py_SETREF(self->last, Py_NewRef(last));1006 }1007 return 0;1008}10091010static PyMemberDef Custom_members[] = {1011 {"number", Py_T_INT, offsetof(CustomObject, number), 0,1012 "custom number"},1013 {NULL} /* Страж */1014};10151016static PyObject *1017Custom_getfirst(CustomObject *self, void *closure)1018{1019 return Py_NewRef(self->first);1020}10211022static int1023Custom_setfirst(CustomObject *self, PyObject *value, void *closure)1024{1025 if (value == NULL) {1026 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");1027 return -1;1028 }1029 if (!PyUnicode_Check(value)) {1030 PyErr_SetString(PyExc_TypeError,1031 "The first attribute value must be a string");1032 return -1;1033 }1034 Py_XSETREF(self->first, Py_NewRef(value));1035 return 0;1036}10371038static PyObject *1039Custom_getlast(CustomObject *self, void *closure)1040{1041 return Py_NewRef(self->last);1042}10431044static int1045Custom_setlast(CustomObject *self, PyObject *value, void *closure)1046{1047 if (value == NULL) {1048 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");1049 return -1;1050 }1051 if (!PyUnicode_Check(value)) {1052 PyErr_SetString(PyExc_TypeError,1053 "The last attribute value must be a string");1054 return -1;1055 }1056 Py_XSETREF(self->last, Py_NewRef(value));1057 return 0;1058}10591060static PyGetSetDef Custom_getsetters[] = {1061 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,1062 "first name", NULL},1063 {"last", (getter) Custom_getlast, (setter) Custom_setlast,1064 "last name", NULL},1065 {NULL} /* Страж */1066};10671068static PyObject *1069Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))1070{1071 return PyUnicode_FromFormat("%S %S", self->first, self->last);1072}10731074static PyMethodDef Custom_methods[] = {1075 {"name", (PyCFunction) Custom_name, METH_NOARGS,1076 "Return the name, combining the first and last name"1077 },1078 {NULL} /* Страж */1079};10801081static PyTypeObject CustomType = {1082 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)1083 .tp_name = "custom4.Custom",1084 .tp_doc = PyDoc_STR("Custom objects"),1085 .tp_basicsize = sizeof(CustomObject),1086 .tp_itemsize = 0,1087 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1088 .tp_new = Custom_new,1089 .tp_init = (initproc) Custom_init,1090 .tp_dealloc = (destructor) Custom_dealloc,1091 .tp_traverse = (traverseproc) Custom_traverse,1092 .tp_clear = (inquiry) Custom_clear,1093 .tp_members = Custom_members,1094 .tp_methods = Custom_methods,1095 .tp_getset = Custom_getsetters,1096};10971098static int1099custom_module_exec(PyObject *m)1100{1101 if (PyType_Ready(&CustomType) < 0) {1102 return -1;1103 }11041105 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {1106 return -1;1107 }11081109 return 0;1110}11111112static PyModuleDef_Slot custom_module_slots[] = {1113 {Py_mod_exec, custom_module_exec},1114 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},1115 {0, NULL}1116};11171118static PyModuleDef custom_module = {1119 .m_base = PyModuleDef_HEAD_INIT,1120 .m_name = "custom4",1121 .m_doc = "Example module that creates an extension type.",1122 .m_size = 0,1123 .m_slots = custom_module_slots,1124};11251126PyMODINIT_FUNC1127PyInit_custom4(void)1128{1129 return PyModuleDef_Init(&custom_module);1130}1131```11321133Во-первых, метод обхода (traversal) сообщает циклическому GC о подобъектах, которые могут участвовать в циклах:11341135```c1136static int1137Custom_traverse(CustomObject *self, visitproc visit, void *arg)1138{1139 int vret;1140 if (self->first) {1141 vret = visit(self->first, arg);1142 if (vret != 0)1143 return vret;1144 }1145 if (self->last) {1146 vret = visit(self->last, arg);1147 if (vret != 0)1148 return vret;1149 }1150 return 0;1151}1152```11531154Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.11551156Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью `Py_VISIT()` можно минимизировать количество шаблонного кода в `Custom_traverse`:11571158```c1159static int1160Custom_traverse(CustomObject *self, visitproc visit, void *arg)1161{1162 Py_VISIT(self->first);1163 Py_VISIT(self->last);1164 return 0;1165}1166```11671168> **Примечание**1169>1170> Реализация [`tp_traverse`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна именовать свои аргументы именно как *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.Py_VISIT).11711172Во-вторых, нужно предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах:11731174```c1175static int1176Custom_clear(CustomObject *self)1177{1178 Py_CLEAR(self->first);1179 Py_CLEAR(self->last);1180 return 0;1181}1182```11831184Обратите внимание на использование макроса [`Py_CLEAR()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_CLEAR). Это рекомендуемый и безопасный способ очистки атрибутов данных произвольных типов с одновременным уменьшением их счётчиков ссылок. Если вместо этого вызвать [`Py_XDECREF()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_XDECREF) для атрибута перед установкой его в `NULL`, существует вероятность, что деструктор атрибута повторно вызовет код, читающий этот атрибут (*особенно* при наличии циклической ссылки).11851186> **Примечание**1187>1188> Можно эмулировать [`Py_CLEAR()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_CLEAR) следующим образом:1189>1190> ```c1191> PyObject *tmp;1192> tmp = self->first;1193> self->first = NULL;1194> Py_XDECREF(tmp);1195> ```1196>1197> Тем не менее, гораздо проще и менее чревато ошибками всегда использовать [`Py_CLEAR()`](https://python-all.ru/3.13/c-api/refcounting.html#c.Py_CLEAR) при удалении атрибута. Не пытайтесь микрооптимизировать в ущерб надёжности!11981199Деаллокатор `Custom_dealloc` может вызывать произвольный код при очистке атрибутов. Это означает, что циклический GC может быть запущен внутри функции. Поскольку GC предполагает, что счётчик ссылок не равен нулю, нужно отменить отслеживание объекта GC с помощью вызова [`PyObject_GC_UnTrack()`](https://python-all.ru/3.13/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой членов. Вот наша переработанная версия деаллокатора с использованием `PyObject_GC_UnTrack()` и `Custom_clear`:12001201```c1202static void1203Custom_dealloc(CustomObject *self)1204{1205 PyObject_GC_UnTrack(self);1206 Custom_clear(self);1207 Py_TYPE(self)->tp_free((PyObject *) self);1208}1209```12101211Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.13/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC) в флаги класса:12121213```c1214.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1215```12161217Вот, в общем-то, и всё. Если бы мы написали собственные обработчики [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_free), их пришлось бы доработать для поддержки циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.12181219## 2.5. Наследование от других типов12201221Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему [`PyTypeObject`](https://python-all.ru/3.13/c-api/type.html#c.PyTypeObject). Совместное использование структур `PyTypeObject` между модулями расширений может быть затруднительным.12221223В этом примере мы создадим тип `SubList`, наследующий от встроенного типа [`list`](https://python-all.ru/3.13/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик:12241225```pycon1226>>> import sublist1227>>> s = sublist.SubList(range(3))1228>>> s.extend(s)1229>>> print(len(s))123061231>>> print(s.increment())123211233>>> print(s.increment())123421235```12361237```c1238#define PY_SSIZE_T_CLEAN1239#include <Python.h>12401241typedef struct {1242 PyListObject list;1243 int state;1244} SubListObject;12451246static PyObject *1247SubList_increment(SubListObject *self, PyObject *unused)1248{1249 self->state++;1250 return PyLong_FromLong(self->state);1251}12521253static PyMethodDef SubList_methods[] = {1254 {"increment", (PyCFunction) SubList_increment, METH_NOARGS,1255 PyDoc_STR("increment state counter")},1256 {NULL},1257};12581259static int1260SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1261{1262 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1263 return -1;1264 self->state = 0;1265 return 0;1266}12671268static PyTypeObject SubListType = {1269 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)1270 .tp_name = "sublist.SubList",1271 .tp_doc = PyDoc_STR("SubList objects"),1272 .tp_basicsize = sizeof(SubListObject),1273 .tp_itemsize = 0,1274 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,1275 .tp_init = (initproc) SubList_init,1276 .tp_methods = SubList_methods,1277};12781279static int1280sublist_module_exec(PyObject *m)1281{1282 SubListType.tp_base = &PyList_Type;1283 if (PyType_Ready(&SubListType) < 0) {1284 return -1;1285 }12861287 if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {1288 return -1;1289 }12901291 return 0;1292}12931294static PyModuleDef_Slot sublist_module_slots[] = {1295 {Py_mod_exec, sublist_module_exec},1296 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},1297 {0, NULL}1298};12991300static PyModuleDef sublist_module = {1301 .m_base = PyModuleDef_HEAD_INIT,1302 .m_name = "sublist",1303 .m_doc = "Example module that creates an extension type.",1304 .m_size = 0,1305 .m_slots = sublist_module_slots,1306};13071308PyMODINIT_FUNC1309PyInit_sublist(void)1310{1311 return PyModuleDef_Init(&sublist_module);1312}1313```13141315Как видите, исходный код очень похож на примеры `Custom` из предыдущих разделов. Разберём основные различия между ними.13161317```c1318typedef struct {1319 PyListObject list;1320 int state;1321} SubListObject;1322```13231324Основное отличие для объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.13/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.13251326Когда объект Python является экземпляром `SubList`, его указатель `PyObject *` можно безопасно приводить как к `PyListObject *`, так и к `SubListObject *`:13271328```c1329static int1330SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1331{1332 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1333 return -1;1334 self->state = 0;1335 return 0;1336}1337```13381339Выше показано, как вызывать метод [`__init__()`](https://python-all.ru/3.13/reference/datamodel.html#object.__init__) базового типа.13401341Этот шаблон важен при написании типа с пользовательскими членами [`tp_new`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_dealloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Обработчик `tp_new` не должен фактически выделять память для объекта с помощью своего [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc), а должен позволить базовому классу обработать это, вызвав его собственный `tp_new`.13421343Структура [`PyTypeObject`](https://python-all.ru/3.13/c-api/type.html#c.PyTypeObject) поддерживает поле [`tp_base`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_base), задающее конкретный базовый класс типа. Из-за проблем с кроссплатформенными компиляторами нельзя заполнять это поле напрямую ссылкой на [`PyList_Type`](https://python-all.ru/3.13/c-api/list.html#c.PyList_Type); это следует делать в функции [`Py_mod_exec`](https://python-all.ru/3.13/c-api/module.html#c.Py_mod_exec):13441345```c1346static int1347sublist_module_exec(PyObject *m)1348{1349 SubListType.tp_base = &PyList_Type;1350 if (PyType_Ready(&SubListType) < 0) {1351 return -1;1352 }13531354 if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {1355 return -1;1356 }13571358 return 0;1359}1360```13611362Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании от существующего типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.13631364После этого вызов [`PyType_Ready()`](https://python-all.ru/3.13/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Custom`.13651366Сноски13671368\[[1](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id1)\]13691370Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой.13711372\[[2](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id2)\]13731374На это полагались в обработчике [`tp_dealloc`](https://python-all.ru/3.13/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) в данном примере, поскольку тип не поддерживает сборку мусора.13751376\[[3](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id3)\]13771378Теперь известно, что первый и последний элементы являются строками, поэтому можно было бы менее осторожно уменьшать их счётчики ссылок. Однако допускаются экземпляры подклассов строк. Даже если освобождение обычных строк не вызовет обратного вызова в наши объекты, нельзя гарантировать, что освобождение экземпляра подкласса строки не вызовет такого обратного вызова.13791380\[[4](https://python-all.ru/3.13/extending/newtypes_tutorial.html#id4)\]13811382Кроме того, даже если атрибуты ограничены экземплярами строк, пользователь может передать произвольные подклассы [`str`](https://python-all.ru/3.13/library/stdtypes.html#str) и тем самым всё равно создать циклические ссылки.1383