newtypes_tutorial.md
1> **Источник:** https://python-all.ru/3.15/extending/newtypes_tutorial.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Определение типов расширений: руководство89Python позволяет разработчику модуля расширения на C определять новые типы, которыми можно манипулировать из кода Python, во многом как встроенные типы [`str`](https://python-all.ru/3.15/library/stdtypes.html#str) и [`list`](https://python-all.ru/3.15/library/stdtypes.html#list). Код для всех типов расширений следует определённому шаблону, но есть некоторые детали, которые необходимо понять, прежде чем приступить к работе. Этот документ является вводным руководством по данной теме.1011## Основы1213Среда выполнения [CPython](https://python-all.ru/3.15/glossary.html#term-CPython) видит все объекты Python как переменные типа [PyObject](https://python-all.ru/3.15/c-api/structures.html#c.PyObject)\*, который служит «базовым типом» для всех объектов Python. Сама структура [`PyObject`](https://python-all.ru/3.15/c-api/structures.html#c.PyObject) содержит только [счётчик ссылок](https://python-all.ru/3.15/glossary.html#term-reference-count) объекта и указатель на его «объект типа». В этом и заключается суть: объект типа определяет, какие (C) функции вызывает интерпретатор, когда, например, выполняется поиск атрибута у объекта, вызов метода или умножение на другой объект. Эти C-функции называются «методами типа».1415Итак, чтобы определить новый тип расширения, необходимо создать новый объект типа.1617Такие вещи можно объяснить только на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип с именем `Custom` внутри C-модуля расширения `custom`:1819> **Примечание**20>21> То, что мы показываем здесь, – это традиционный способ определения *статических* типов расширений. Он должен быть достаточным для большинства случаев. C API также позволяет определять типы расширений, размещаемые в куче, с помощью функции [`PyType_FromSpec()`](https://python-all.ru/3.15/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.15/c-api/structures.html#c.PyObject), содержащее указатель на объект типа и счётчик ссылок (к ним можно получить доступ с помощью макросов [`Py_TYPE`](https://python-all.ru/3.15/c-api/structures.html#c.Py_TYPE) и [`Py_REFCNT`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_REFCNT) соответственно). Смысл макроса – абстрагироваться от расположения полей и разрешить дополнительные поля в [отладочных сборках](https://python-all.ru/3.15/using/configure.html#debug-build).9394> **Примечание**95>96> В приведённом выше коде нет точки с запятой после макроса [`PyObject_HEAD`](https://python-all.ru/3.15/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.15/c-api/type.html#c.PyTypeObject), которые не важны, а также не беспокоиться о порядке объявления полей.124125Фактическое определение [`PyTypeObject`](https://python-all.ru/3.15/c-api/type.html#c.PyTypeObject) в `object.h` содержит гораздо больше [полей](https://python-all.ru/3.15/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.15/library/pydoc.html#module-pydoc) и [`pickle`](https://python-all.ru/3.15/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.15/c-api/typeobj.html#c.PyTypeObject.tp_itemsize) используется только для объектов переменного размера, в остальных случаях должно быть равно нулю.156157> **Примечание**158>159> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.15/reference/datamodel.html#type.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для `tp_basicsize` вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.15/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.160161Мы устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.15/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.15/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.15/c-api/typeobj.html#c.PyTypeObject.tp_new). Это эквивалент метода Python [`__new__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__new__), но должен быть указан явно. В данном случае мы можем просто использовать реализацию по умолчанию, предоставляемую API-функцией [`PyType_GenericNew()`](https://python-all.ru/3.15/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.15/c-api/structures.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## Добавление данных и методов к базовому примеру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(PyObject *op)253{254 CustomObject *self = (CustomObject *) op;255 Py_XDECREF(self->first);256 Py_XDECREF(self->last);257 Py_TYPE(self)->tp_free(self);258}259260static PyObject *261Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)262{263 CustomObject *self;264 self = (CustomObject *) type->tp_alloc(type, 0);265 if (self != NULL) {266 self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);267 if (self->first == NULL) {268 Py_DECREF(self);269 return NULL;270 }271 self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);272 if (self->last == NULL) {273 Py_DECREF(self);274 return NULL;275 }276 self->number = 0;277 }278 return (PyObject *) self;279}280281static int282Custom_init(PyObject *op, PyObject *args, PyObject *kwds)283{284 CustomObject *self = (CustomObject *) op;285 static char *kwlist[] = {"first", "last", "number", NULL};286 PyObject *first = NULL, *last = NULL;287288 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,289 &first, &last,290 &self->number))291 return -1;292293 if (first) {294 Py_XSETREF(self->first, Py_NewRef(first));295 }296 if (last) {297 Py_XSETREF(self->last, Py_NewRef(last));298 }299 return 0;300}301302static PyMemberDef Custom_members[] = {303 {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,304 "first name"},305 {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,306 "last name"},307 {"number", Py_T_INT, offsetof(CustomObject, number), 0,308 "custom number"},309 {NULL} /* Страж */310};311312static PyObject *313Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))314{315 CustomObject *self = (CustomObject *) op;316 if (self->first == NULL) {317 PyErr_SetString(PyExc_AttributeError, "first");318 return NULL;319 }320 if (self->last == NULL) {321 PyErr_SetString(PyExc_AttributeError, "last");322 return NULL;323 }324 return PyUnicode_FromFormat("%S %S", self->first, self->last);325}326327static PyMethodDef Custom_methods[] = {328 {"name", Custom_name, METH_NOARGS,329 "Return the name, combining the first and last name"330 },331 {NULL} /* Страж */332};333334static PyTypeObject CustomType = {335 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)336 .tp_name = "custom2.Custom",337 .tp_doc = PyDoc_STR("Custom objects"),338 .tp_basicsize = sizeof(CustomObject),339 .tp_itemsize = 0,340 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,341 .tp_new = Custom_new,342 .tp_init = Custom_init,343 .tp_dealloc = Custom_dealloc,344 .tp_members = Custom_members,345 .tp_methods = Custom_methods,346};347348static int349custom_module_exec(PyObject *m)350{351 if (PyType_Ready(&CustomType) < 0) {352 return -1;353 }354355 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {356 return -1;357 }358359 return 0;360}361362static PyModuleDef_Slot custom_module_slots[] = {363 {Py_mod_exec, custom_module_exec},364 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},365 {0, NULL}366};367368static PyModuleDef custom_module = {369 .m_base = PyModuleDef_HEAD_INIT,370 .m_name = "custom2",371 .m_doc = "Example module that creates an extension type.",372 .m_size = 0,373 .m_slots = custom_module_slots,374};375376PyMODINIT_FUNC377PyInit_custom2(void)378{379 return PyModuleDef_Init(&custom_module);380}381```382383Эта версия модуля содержит ряд изменений.384385Тип `Custom` теперь имеет три атрибута данных в своей C-структуре: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – это целое число C.386387Структура объекта обновляется соответствующим образом:388389```c390typedef struct {391 PyObject_HEAD392 PyObject *first; /* имя */393 PyObject *last; /* фамилия */394 int number;395} CustomObject;396```397398Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:399400```c401static void402Custom_dealloc(PyObject *op)403{404 CustomObject *self = (CustomObject *) op;405 Py_XDECREF(self->first);406 Py_XDECREF(self->last);407 Py_TYPE(self)->tp_free(self);408}409```410411который присваивается члену [`tp_dealloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):412413```c414.tp_dealloc = Custom_dealloc,415```416417Этот метод сначала сбрасывает счётчики ссылок двух атрибутов Python. [`Py_XDECREF()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_XDECREF) корректно обрабатывает случай, когда его аргумент равен `NULL` (что может произойти, если `tp_new` не выполнился до конца). Затем он вызывает член [`tp_free`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта (вычисленный с помощью `Py_TYPE(self)`) для освобождения памяти объекта. Обратите внимание, что тип объекта может не быть `CustomType`, поскольку объект может быть экземпляром подкласса.418419> **Примечание**420>421> Явное приведение к `CustomObject *` выше необходимо, потому что мы определили `Custom_dealloc` как принимающий аргумент типа `PyObject *`, в то время как указатель на функцию `tp_dealloc` ожидает получения аргумента `PyObject *`. Присваивая слоту `tp_dealloc` типа, мы объявляем, что он может быть вызван только с экземплярами нашего класса `CustomObject`, поэтому приведение к `(CustomObject *)` безопасно. Это объектно-ориентированный полиморфизм на C!422>423> В существующем коде или в предыдущих версиях этого руководства вы могли видеть похожие функции, принимающие непосредственно указатель на структуру объекта подтипа (`CustomObject*`), например:424>425> ```c426> Custom_dealloc(CustomObject *self)427> {428> Py_XDECREF(self->first);429> Py_XDECREF(self->last);430> Py_TYPE(self)->tp_free((PyObject *) self);431> }432> ...433> .tp_dealloc = (destructor) Custom_dealloc,434> ```435>436> Это делает то же самое на всех архитектурах, поддерживаемых CPython, но согласно стандарту C это вызывает неопределённое поведение.437438Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому мы предоставляем реализацию `tp_new`:439440```c441static PyObject *442Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)443{444 CustomObject *self;445 self = (CustomObject *) type->tp_alloc(type, 0);446 if (self != NULL) {447 self->first = PyUnicode_FromString("");448 if (self->first == NULL) {449 Py_DECREF(self);450 return NULL;451 }452 self->last = PyUnicode_FromString("");453 if (self->last == NULL) {454 Py_DECREF(self);455 return NULL;456 }457 self->number = 0;458 }459 return (PyObject *) self;460}461```462463и устанавливаем его в член [`tp_new`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_new):464465```c466.tp_new = Custom_new,467```468469Обработчик `tp_new` отвечает за создание (в отличие от инициализации) объектов типа. Он предоставляется в Python как метод [`__new__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__new__). Не требуется определять член `tp_new`, и действительно, многие расширения типов просто повторно используют [`PyType_GenericNew()`](https://python-all.ru/3.15/c-api/type.html#c.PyType_GenericNew), как это сделано в первой версии типа `Custom` выше. В этом случае мы используем обработчик `tp_new` для инициализации атрибутов `first` и `last` значениями по умолчанию, отличными от `NULL`.470471`tp_new` передаётся тип, экземпляр которого создаётся (не обязательно `CustomType`, если создаётся экземпляр подкласса), и любые аргументы, переданные при вызове типа; ожидается, что он вернёт созданный экземпляр. Обработчики `tp_new` всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам (также известным как `tp_init` в C или `__init__` в Python).472473> **Примечание**474>475> `tp_new` не должен вызывать `tp_init` явно, так как интерпретатор сделает это сам.476477Реализация `tp_new` вызывает слот [`tp_alloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти:478479```c480self = (CustomObject *) type->tp_alloc(type, 0);481```482483Поскольку выделение памяти может завершиться неудачей, мы должны проверить результат [`tp_alloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_alloc) на `NULL` перед продолжением.484485> **Примечание**486>487> Мы не заполняли слот [`tp_alloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.15/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.15/library/functions.html#object). Большинство типов используют стратегию выделения по умолчанию.488489> **Примечание**490>491> При создании кооперативного [`tp_new`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_new) (такого, который вызывает `tp_new` или [`__new__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__new__) базового типа) нельзя *не* пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте тип, который будете вызывать, и вызывайте его `tp_new` напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуются от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.15/library/exceptions.html#TypeError).)492493Мы также определяем функцию инициализации, которая принимает аргументы для предоставления начальных значений экземпляру:494495```c496static int497Custom_init(PyObject *op, PyObject *args, PyObject *kwds)498{499 CustomObject *self = (CustomObject *) op;500 static char *kwlist[] = {"first", "last", "number", NULL};501 PyObject *first = NULL, *last = NULL, *tmp;502503 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,504 &first, &last,505 &self->number))506 return -1;507508 if (first) {509 tmp = self->first;510 Py_INCREF(first);511 self->first = first;512 Py_XDECREF(tmp);513 }514 if (last) {515 tmp = self->last;516 Py_INCREF(last);517 self->last = last;518 Py_XDECREF(tmp);519 }520 return 0;521}522```523524заполняя слот [`tp_init`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_init).525526```c527.tp_init = Custom_init,528```529530Слот [`tp_init`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_init) открыт в Python как метод [`__init__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. Инициализаторы всегда принимают позиционные и именованные аргументы и должны возвращать либо `0` в случае успеха, либо `-1` в случае ошибки.531532В отличие от обработчика `tp_new`, нет никакой гарантии, что `tp_init` будет вызван (например, модуль [`pickle`](https://python-all.ru/3.15/library/pickle.html#module-pickle) по умолчанию не вызывает [`__init__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__init__) для распакованных экземпляров). Он также может быть вызван несколько раз. Кто угодно может вызвать метод `__init__()` на наших объектах. По этой причине нужно быть особенно осторожным при присвоении новых значений атрибутов. Может возникнуть соблазн, например, присвоить члену `first` такое значение:533534```c535if (first) {536 Py_XDECREF(self->first);537 Py_INCREF(first);538 self->first = first;539}540```541542Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому это может быть любой объект. Он может иметь деструктор, который приводит к выполнению кода, пытающегося получить доступ к члену `first`; или этот деструктор может отсоединить [состояние потока](https://python-all.ru/3.15/glossary.html#term-attached-thread-state) и позволить произвольному коду выполняться в других потоках, получающих доступ и изменяющих наш объект.543544Чтобы быть параноидальными и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?545546- когда мы точно знаем, что счётчик ссылок больше 1;547- когда мы знаем, что освобождение объекта [\[1\]](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id5) не отсоединит [состояние потока](https://python-all.ru/3.15/glossary.html#term-attached-thread-state) и не вызовет обратных вызовов в код нашего типа;548- когда уменьшается счётчик ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) для типа, который не поддерживает циклическую сборку мусора [\[2\]](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id6).549550Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:551552```c553static PyMemberDef Custom_members[] = {554 {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,555 "first name"},556 {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,557 "last name"},558 {"number", Py_T_INT, offsetof(CustomObject, number), 0,559 "custom number"},560 {NULL} /* Страж */561};562```563564и поместить определения в слот [`tp_members`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_members):565566```c567.tp_members = Custom_members,568```569570Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Общее управление атрибутами](https://python-all.ru/3.15/extending/newtypes.html#generic-attribute-management) ниже.571572Недостаток этого подхода в том, что он не даёт способа ограничить типы объектов, которые можно присваивать атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Кроме того, атрибуты можно удалить, установив указатели C в `NULL`. Даже если мы можем гарантировать, что члены инициализируются значениями, отличными от `NULL`, члены могут быть установлены в `NULL`, если атрибуты удалены.573574Мы определяем один метод `Custom.name()`, который выводит имя объекта как конкатенацию имени и фамилии.575576```c577static PyObject *578Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))579{580 CustomObject *self = (CustomObject *) op;581 if (self->first == NULL) {582 PyErr_SetString(PyExc_AttributeError, "first");583 return NULL;584 }585 if (self->last == NULL) {586 PyErr_SetString(PyExc_AttributeError, "last");587 return NULL;588 }589 return PyUnicode_FromFormat("%S %S", self->first, self->last);590}591```592593Метод реализован как функция на C, которая принимает экземпляр `Custom` (или подкласс `Custom`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:594595```python596def name(self):597 return "%s %s" % (self.first, self.last)598```599600Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны `NULL`. Это потому, что их можно удалить, и в этом случае они устанавливаются в `NULL`. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Как это сделать, мы увидим в следующем разделе.601602Теперь, когда мы определили метод, нужно создать массив определений методов:603604```c605static PyMethodDef Custom_methods[] = {606 {"name", Custom_name, METH_NOARGS,607 "Return the name, combining the first and last name"608 },609 {NULL} /* Страж */610};611```612613(обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.15/c-api/structures.html#c.METH_NOARGS), чтобы указать, что метод не ожидает аргументов, кроме *self*)614615и назначить его слоту [`tp_methods`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_methods):616617```c618.tp_methods = Custom_methods,619```620621Наконец, сделаем наш тип пригодным для использования в качестве базового класса для создания подклассов. Мы написали наши методы достаточно осторожно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.15/c-api/typeobj.html#c.Py_TPFLAGS_BASETYPE) в определение флага нашего класса:622623```c624.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,625```626627Мы переименовываем `PyInit_custom()` в `PyInit_custom2()`, обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.15/c-api/module.html#c.PyModuleDef) и обновляем полное имя класса в структуре [`PyTypeObject`](https://python-all.ru/3.15/c-api/type.html#c.PyTypeObject).628629Наконец, обновляем файл `setup.py`, чтобы включить новый модуль,630631```python632from setuptools import Extension, setup633setup(ext_modules=[634 Extension("custom", ["custom.c"]),635 Extension("custom2", ["custom2.c"]),636])637```638639а затем переустанавливаем, чтобы можно было `import custom2`:640641```console642$ python -m pip install .643```644645## Более тонкое управление атрибутами данных646647В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Custom`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.648649```c650#define PY_SSIZE_T_CLEAN651#include <Python.h>652#include <stddef.h> /* для offsetof() */653654typedef struct {655 PyObject_HEAD656 PyObject *first; /* имя */657 PyObject *last; /* фамилия */658 int number;659} CustomObject;660661static void662Custom_dealloc(PyObject *op)663{664 CustomObject *self = (CustomObject *) op;665 Py_XDECREF(self->first);666 Py_XDECREF(self->last);667 Py_TYPE(self)->tp_free(self);668}669670static PyObject *671Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)672{673 CustomObject *self;674 self = (CustomObject *) type->tp_alloc(type, 0);675 if (self != NULL) {676 self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);677 if (self->first == NULL) {678 Py_DECREF(self);679 return NULL;680 }681 self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);682 if (self->last == NULL) {683 Py_DECREF(self);684 return NULL;685 }686 self->number = 0;687 }688 return (PyObject *) self;689}690691static int692Custom_init(PyObject *op, PyObject *args, PyObject *kwds)693{694 CustomObject *self = (CustomObject *) op;695 static char *kwlist[] = {"first", "last", "number", NULL};696 PyObject *first = NULL, *last = NULL;697698 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,699 &first, &last,700 &self->number))701 return -1;702703 if (first) {704 Py_SETREF(self->first, Py_NewRef(first));705 }706 if (last) {707 Py_SETREF(self->last, Py_NewRef(last));708 }709 return 0;710}711712static PyMemberDef Custom_members[] = {713 {"number", Py_T_INT, offsetof(CustomObject, number), 0,714 "custom number"},715 {NULL} /* Страж */716};717718static PyObject *719Custom_getfirst(PyObject *op, void *closure)720{721 CustomObject *self = (CustomObject *) op;722 return Py_NewRef(self->first);723}724725static int726Custom_setfirst(PyObject *op, PyObject *value, void *closure)727{728 CustomObject *self = (CustomObject *) op;729 if (value == NULL) {730 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");731 return -1;732 }733 if (!PyUnicode_Check(value)) {734 PyErr_SetString(PyExc_TypeError,735 "The first attribute value must be a string");736 return -1;737 }738 Py_SETREF(self->first, Py_NewRef(value));739 return 0;740}741742static PyObject *743Custom_getlast(PyObject *op, void *closure)744{745 CustomObject *self = (CustomObject *) op;746 return Py_NewRef(self->last);747}748749static int750Custom_setlast(PyObject *op, PyObject *value, void *closure)751{752 CustomObject *self = (CustomObject *) op;753 if (value == NULL) {754 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");755 return -1;756 }757 if (!PyUnicode_Check(value)) {758 PyErr_SetString(PyExc_TypeError,759 "The last attribute value must be a string");760 return -1;761 }762 Py_SETREF(self->last, Py_NewRef(value));763 return 0;764}765766static PyGetSetDef Custom_getsetters[] = {767 {"first", Custom_getfirst, Custom_setfirst,768 "first name", NULL},769 {"last", Custom_getlast, Custom_setlast,770 "last name", NULL},771 {NULL} /* Страж */772};773774static PyObject *775Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))776{777 CustomObject *self = (CustomObject *) op;778 return PyUnicode_FromFormat("%S %S", self->first, self->last);779}780781static PyMethodDef Custom_methods[] = {782 {"name", Custom_name, METH_NOARGS,783 "Return the name, combining the first and last name"784 },785 {NULL} /* Страж */786};787788static PyTypeObject CustomType = {789 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)790 .tp_name = "custom3.Custom",791 .tp_doc = PyDoc_STR("Custom objects"),792 .tp_basicsize = sizeof(CustomObject),793 .tp_itemsize = 0,794 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,795 .tp_new = Custom_new,796 .tp_init = Custom_init,797 .tp_dealloc = Custom_dealloc,798 .tp_members = Custom_members,799 .tp_methods = Custom_methods,800 .tp_getset = Custom_getsetters,801};802803static int804custom_module_exec(PyObject *m)805{806 if (PyType_Ready(&CustomType) < 0) {807 return -1;808 }809810 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {811 return -1;812 }813814 return 0;815}816817static PyModuleDef_Slot custom_module_slots[] = {818 {Py_mod_exec, custom_module_exec},819 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},820 {0, NULL}821};822823static PyModuleDef custom_module = {824 .m_base = PyModuleDef_HEAD_INIT,825 .m_name = "custom3",826 .m_doc = "Example module that creates an extension type.",827 .m_size = 0,828 .m_slots = custom_module_slots,829};830831PyMODINIT_FUNC832PyInit_custom3(void)833{834 return PyModuleDef_Init(&custom_module);835}836```837838Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:839840```c841static PyObject *842Custom_getfirst(PyObject *op, void *closure)843{844 CustomObject *self = (CustomObject *) op;845 Py_INCREF(self->first);846 return self->first;847}848849static int850Custom_setfirst(PyObject *op, PyObject *value, void *closure)851{852 CustomObject *self = (CustomObject *) op;853 PyObject *tmp;854 if (value == NULL) {855 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");856 return -1;857 }858 if (!PyUnicode_Check(value)) {859 PyErr_SetString(PyExc_TypeError,860 "The first attribute value must be a string");861 return -1;862 }863 tmp = self->first;864 Py_INCREF(value);865 self->first = value;866 Py_DECREF(tmp);867 return 0;868}869```870871Функция получения принимает объект `Custom` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это может быть использовано, например, для создания единого набора функций получения и установки, которые решают, какой атрибут получить или установить на основе данных в замыкании.)872873Функция установки принимает объект `Custom`, новое значение и замыкание. Новое значение может быть `NULL`, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или его новое значение не является строкой.874875Мы создаём массив структур [`PyGetSetDef`](https://python-all.ru/3.15/c-api/structures.html#c.PyGetSetDef):876877```c878static PyGetSetDef Custom_getsetters[] = {879 {"first", Custom_getfirst, Custom_setfirst,880 "first name", NULL},881 {"last", Custom_getlast, Custom_setlast,882 "last name", NULL},883 {NULL} /* Страж */884};885```886887и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_getset):888889```c890.tp_getset = Custom_getsetters,891```892893Последний элемент структуры [`PyGetSetDef`](https://python-all.ru/3.15/c-api/structures.html#c.PyGetSetDef) – это «замыкание», упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём `NULL`.894895Также удаляем определения членов для этих атрибутов:896897```c898static PyMemberDef Custom_members[] = {899 {"number", Py_T_INT, offsetof(CustomObject, number), 0,900 "custom number"},901 {NULL} /* Страж */902};903```904905Также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id7):906907```c908static int909Custom_init(PyObject *op, PyObject *args, PyObject *kwds)910{911 CustomObject *self = (CustomObject *) op;912 static char *kwlist[] = {"first", "last", "number", NULL};913 PyObject *first = NULL, *last = NULL, *tmp;914915 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,916 &first, &last,917 &self->number))918 return -1;919920 if (first) {921 tmp = self->first;922 Py_INCREF(first);923 self->first = first;924 Py_DECREF(tmp);925 }926 if (last) {927 tmp = self->last;928 Py_INCREF(last);929 self->last = last;930 Py_DECREF(tmp);931 }932 return 0;933}934```935936Благодаря этим изменениям можно гарантировать, что члены `first` и `last` никогда не будут `NULL`, поэтому проверки на значения `NULL` можно убрать почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_DECREF). Единственное место, где эти вызовы нельзя изменить, – реализация `tp_dealloc`, где возможна ситуация, что инициализация этих членов завершилась неудачей в `tp_new`.937938Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.939940## Поддержка циклической сборки мусора941942Python имеет [циклический сборщик мусора (GC)](https://python-all.ru/3.15/glossary.html#term-garbage-collection), который может определять ненужные объекты даже при ненулевом счётчике ссылок. Это возможно, когда объекты образуют циклы. Например, рассмотрим:943944```pycon945>>> l = []946>>> l.append(l)947>>> del l948```949950В этом примере создаётся список, содержащий сам себя. При его удалении у него всё ещё остаётся ссылка на самого себя. Счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.951952Во второй версии примера `Custom` мы разрешили хранить объекты любого типа в атрибутах `first` или `last` [\[4\]](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id8). Кроме того, во второй и третьей версиях мы разрешили наследование от `Custom`, а подклассы могут добавлять произвольные атрибуты. По любой из этих двух причин объекты `Custom` могут участвовать в циклах:953954```pycon955>>> import custom3956>>> class Derived(custom3.Custom): pass957...958>>> n = Derived()959>>> n.some_attribute = n960```961962Чтобы экземпляр `Custom`, участвующий в цикле ссылок, был корректно обнаружен и собран циклическим GC, наш тип `Custom` должен заполнить два дополнительных слота и включить флаг, активирующий эти слоты:963964```c965#define PY_SSIZE_T_CLEAN966#include <Python.h>967#include <stddef.h> /* для offsetof() */968969typedef struct {970 PyObject_HEAD971 PyObject *first; /* имя */972 PyObject *last; /* фамилия */973 int number;974} CustomObject;975976static int977Custom_traverse(PyObject *op, visitproc visit, void *arg)978{979 CustomObject *self = (CustomObject *) op;980 Py_VISIT(self->first);981 Py_VISIT(self->last);982 return 0;983}984985static int986Custom_clear(PyObject *op)987{988 CustomObject *self = (CustomObject *) op;989 Py_CLEAR(self->first);990 Py_CLEAR(self->last);991 return 0;992}993994static void995Custom_dealloc(PyObject *op)996{997 PyObject_GC_UnTrack(op);998 (void)Custom_clear(op);999 Py_TYPE(op)->tp_free(op);1000}10011002static PyObject *1003Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1004{1005 CustomObject *self;1006 self = (CustomObject *) type->tp_alloc(type, 0);1007 if (self != NULL) {1008 self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);1009 if (self->first == NULL) {1010 Py_DECREF(self);1011 return NULL;1012 }1013 self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);1014 if (self->last == NULL) {1015 Py_DECREF(self);1016 return NULL;1017 }1018 self->number = 0;1019 }1020 return (PyObject *) self;1021}10221023static int1024Custom_init(PyObject *op, PyObject *args, PyObject *kwds)1025{1026 CustomObject *self = (CustomObject *) op;1027 static char *kwlist[] = {"first", "last", "number", NULL};1028 PyObject *first = NULL, *last = NULL;10291030 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,1031 &first, &last,1032 &self->number))1033 return -1;10341035 if (first) {1036 Py_SETREF(self->first, Py_NewRef(first));1037 }1038 if (last) {1039 Py_SETREF(self->last, Py_NewRef(last));1040 }1041 return 0;1042}10431044static PyMemberDef Custom_members[] = {1045 {"number", Py_T_INT, offsetof(CustomObject, number), 0,1046 "custom number"},1047 {NULL} /* Страж */1048};10491050static PyObject *1051Custom_getfirst(PyObject *op, void *closure)1052{1053 CustomObject *self = (CustomObject *) op;1054 return Py_NewRef(self->first);1055}10561057static int1058Custom_setfirst(PyObject *op, PyObject *value, void *closure)1059{1060 CustomObject *self = (CustomObject *) op;1061 if (value == NULL) {1062 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");1063 return -1;1064 }1065 if (!PyUnicode_Check(value)) {1066 PyErr_SetString(PyExc_TypeError,1067 "The first attribute value must be a string");1068 return -1;1069 }1070 Py_XSETREF(self->first, Py_NewRef(value));1071 return 0;1072}10731074static PyObject *1075Custom_getlast(PyObject *op, void *closure)1076{1077 CustomObject *self = (CustomObject *) op;1078 return Py_NewRef(self->last);1079}10801081static int1082Custom_setlast(PyObject *op, PyObject *value, void *closure)1083{1084 CustomObject *self = (CustomObject *) op;1085 if (value == NULL) {1086 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");1087 return -1;1088 }1089 if (!PyUnicode_Check(value)) {1090 PyErr_SetString(PyExc_TypeError,1091 "The last attribute value must be a string");1092 return -1;1093 }1094 Py_XSETREF(self->last, Py_NewRef(value));1095 return 0;1096}10971098static PyGetSetDef Custom_getsetters[] = {1099 {"first", Custom_getfirst, Custom_setfirst,1100 "first name", NULL},1101 {"last", Custom_getlast, Custom_setlast,1102 "last name", NULL},1103 {NULL} /* Страж */1104};11051106static PyObject *1107Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))1108{1109 CustomObject *self = (CustomObject *) op;1110 return PyUnicode_FromFormat("%S %S", self->first, self->last);1111}11121113static PyMethodDef Custom_methods[] = {1114 {"name", Custom_name, METH_NOARGS,1115 "Return the name, combining the first and last name"1116 },1117 {NULL} /* Страж */1118};11191120static PyTypeObject CustomType = {1121 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)1122 .tp_name = "custom4.Custom",1123 .tp_doc = PyDoc_STR("Custom objects"),1124 .tp_basicsize = sizeof(CustomObject),1125 .tp_itemsize = 0,1126 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1127 .tp_new = Custom_new,1128 .tp_init = Custom_init,1129 .tp_dealloc = Custom_dealloc,1130 .tp_traverse = Custom_traverse,1131 .tp_clear = Custom_clear,1132 .tp_members = Custom_members,1133 .tp_methods = Custom_methods,1134 .tp_getset = Custom_getsetters,1135};11361137static int1138custom_module_exec(PyObject *m)1139{1140 if (PyType_Ready(&CustomType) < 0) {1141 return -1;1142 }11431144 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {1145 return -1;1146 }11471148 return 0;1149}11501151static PyModuleDef_Slot custom_module_slots[] = {1152 {Py_mod_exec, custom_module_exec},1153 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},1154 {0, NULL}1155};11561157static PyModuleDef custom_module = {1158 .m_base = PyModuleDef_HEAD_INIT,1159 .m_name = "custom4",1160 .m_doc = "Example module that creates an extension type.",1161 .m_size = 0,1162 .m_slots = custom_module_slots,1163};11641165PyMODINIT_FUNC1166PyInit_custom4(void)1167{1168 return PyModuleDef_Init(&custom_module);1169}1170```11711172Во-первых, метод обхода (traversal) сообщает циклическому GC о подобъектах, которые могут участвовать в циклах:11731174```c1175static int1176Custom_traverse(PyObject *op, visitproc visit, void *arg)1177{1178 CustomObject *self = (CustomObject *) op;1179 int vret;1180 if (self->first) {1181 vret = visit(self->first, arg);1182 if (vret != 0)1183 return vret;1184 }1185 if (self->last) {1186 vret = visit(self->last, arg);1187 if (vret != 0)1188 return vret;1189 }1190 return 0;1191}1192```11931194Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.11951196Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.15/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью `Py_VISIT()` можно минимизировать количество шаблонного кода в `Custom_traverse`:11971198```c1199static int1200Custom_traverse(PyObject *op, visitproc visit, void *arg)1201{1202 CustomObject *self = (CustomObject *) op;1203 Py_VISIT(self->first);1204 Py_VISIT(self->last);1205 return 0;1206}1207```12081209> **Примечание**1210>1211> Реализация [`tp_traverse`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна именовать свои аргументы именно как *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.15/c-api/gcsupport.html#c.Py_VISIT).12121213Во-вторых, нужно предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах:12141215```c1216static int1217Custom_clear(PyObject *op)1218{1219 CustomObject *self = (CustomObject *) op;1220 Py_CLEAR(self->first);1221 Py_CLEAR(self->last);1222 return 0;1223}1224```12251226Обратите внимание на использование макроса [`Py_CLEAR()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_CLEAR). Это рекомендуемый и безопасный способ очистки атрибутов данных произвольных типов с одновременным уменьшением их счётчиков ссылок. Если вместо этого вызвать [`Py_XDECREF()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_XDECREF) для атрибута перед установкой его в `NULL`, существует вероятность, что деструктор атрибута повторно вызовет код, читающий этот атрибут (*особенно* при наличии циклической ссылки).12271228> **Примечание**1229>1230> Можно эмулировать [`Py_CLEAR()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_CLEAR) следующим образом:1231>1232> ```c1233> PyObject *tmp;1234> tmp = self->first;1235> self->first = NULL;1236> Py_XDECREF(tmp);1237> ```1238>1239> Тем не менее, гораздо проще и менее чревато ошибками всегда использовать [`Py_CLEAR()`](https://python-all.ru/3.15/c-api/refcounting.html#c.Py_CLEAR) при удалении атрибута. Не пытайтесь микрооптимизировать в ущерб надёжности!12401241Деаллокатор `Custom_dealloc` может вызывать произвольный код при очистке атрибутов. Это означает, что циклический GC может быть запущен внутри функции. Поскольку GC предполагает, что счётчик ссылок не равен нулю, нужно отменить отслеживание объекта GC с помощью вызова [`PyObject_GC_UnTrack()`](https://python-all.ru/3.15/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой членов. Вот наша переработанная версия деаллокатора с использованием `PyObject_GC_UnTrack()` и `Custom_clear`:12421243```c1244static void1245Custom_dealloc(PyObject *op)1246{1247 PyObject_GC_UnTrack(op);1248 (void)Custom_clear(op);1249 Py_TYPE(op)->tp_free(op);1250}1251```12521253Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.15/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC) в флаги класса:12541255```c1256.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1257```12581259Вот, в общем-то, и всё. Если бы мы написали собственные обработчики [`tp_alloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_free), их пришлось бы доработать для поддержки циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.12601261## Наследование от других типов12621263Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему [`PyTypeObject`](https://python-all.ru/3.15/c-api/type.html#c.PyTypeObject). Совместное использование структур `PyTypeObject` между модулями расширений может быть затруднительным.12641265В этом примере мы создадим тип `SubList`, наследующий от встроенного типа [`list`](https://python-all.ru/3.15/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик:12661267```pycon1268>>> import sublist1269>>> s = sublist.SubList(range(3))1270>>> s.extend(s)1271>>> print(len(s))127261273>>> print(s.increment())127411275>>> print(s.increment())127621277```12781279```c1280#define PY_SSIZE_T_CLEAN1281#include <Python.h>12821283typedef struct {1284 PyListObject list;1285 int state;1286} SubListObject;12871288static PyObject *1289SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy))1290{1291 SubListObject *self = (SubListObject *) op;1292 self->state++;1293 return PyLong_FromLong(self->state);1294}12951296static PyMethodDef SubList_methods[] = {1297 {"increment", SubList_increment, METH_NOARGS,1298 PyDoc_STR("increment state counter")},1299 {NULL},1300};13011302static int1303SubList_init(PyObject *op, PyObject *args, PyObject *kwds)1304{1305 SubListObject *self = (SubListObject *) op;1306 if (PyList_Type.tp_init(op, args, kwds) < 0)1307 return -1;1308 self->state = 0;1309 return 0;1310}13111312static PyTypeObject SubListType = {1313 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)1314 .tp_name = "sublist.SubList",1315 .tp_doc = PyDoc_STR("SubList objects"),1316 .tp_basicsize = sizeof(SubListObject),1317 .tp_itemsize = 0,1318 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,1319 .tp_init = SubList_init,1320 .tp_methods = SubList_methods,1321};13221323static int1324sublist_module_exec(PyObject *m)1325{1326 SubListType.tp_base = &PyList_Type;1327 if (PyType_Ready(&SubListType) < 0) {1328 return -1;1329 }13301331 if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {1332 return -1;1333 }13341335 return 0;1336}13371338static PyModuleDef_Slot sublist_module_slots[] = {1339 {Py_mod_exec, sublist_module_exec},1340 {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},1341 {0, NULL}1342};13431344static PyModuleDef sublist_module = {1345 .m_base = PyModuleDef_HEAD_INIT,1346 .m_name = "sublist",1347 .m_doc = "Example module that creates an extension type.",1348 .m_size = 0,1349 .m_slots = sublist_module_slots,1350};13511352PyMODINIT_FUNC1353PyInit_sublist(void)1354{1355 return PyModuleDef_Init(&sublist_module);1356}1357```13581359Как видите, исходный код очень похож на примеры `Custom` из предыдущих разделов. Разберём основные различия между ними.13601361```c1362typedef struct {1363 PyListObject list;1364 int state;1365} SubListObject;1366```13671368Основное отличие для объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.15/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.13691370Когда объект Python является экземпляром `SubList`, его указатель `PyObject *` можно безопасно приводить как к `PyListObject *`, так и к `SubListObject *`:13711372```c1373static int1374SubList_init(PyObject *op, PyObject *args, PyObject *kwds)1375{1376 SubListObject *self = (SubListObject *) op;1377 if (PyList_Type.tp_init(op, args, kwds) < 0)1378 return -1;1379 self->state = 0;1380 return 0;1381}1382```13831384Выше показано, как вызывать метод [`__init__()`](https://python-all.ru/3.15/reference/datamodel.html#object.__init__) базового типа.13851386Этот шаблон важен при написании типа с пользовательскими членами [`tp_new`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_dealloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Обработчик `tp_new` не должен фактически выделять память для объекта с помощью своего [`tp_alloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_alloc), а должен позволить базовому классу обработать это, вызвав его собственный `tp_new`.13871388Структура [`PyTypeObject`](https://python-all.ru/3.15/c-api/type.html#c.PyTypeObject) поддерживает поле [`tp_base`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_base), задающее конкретный базовый класс типа. Из-за проблем с кроссплатформенными компиляторами нельзя заполнять это поле напрямую ссылкой на [`PyList_Type`](https://python-all.ru/3.15/c-api/list.html#c.PyList_Type); это следует делать в функции [`Py_mod_exec`](https://python-all.ru/3.15/c-api/module.html#c.Py_mod_exec):13891390```c1391static int1392sublist_module_exec(PyObject *m)1393{1394 SubListType.tp_base = &PyList_Type;1395 if (PyType_Ready(&SubListType) < 0) {1396 return -1;1397 }13981399 if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {1400 return -1;1401 }14021403 return 0;1404}1405```14061407Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.15/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании от существующего типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.15/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.14081409После этого вызов [`PyType_Ready()`](https://python-all.ru/3.15/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Custom`.14101411Сноски14121413\[[1](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id1)\]14141415Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой.14161417\[[2](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id2)\]14181419На это полагались в обработчике [`tp_dealloc`](https://python-all.ru/3.15/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) в данном примере, поскольку тип не поддерживает сборку мусора.14201421\[[3](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id3)\]14221423Теперь известно, что первый и последний элементы являются строками, поэтому можно было бы менее осторожно уменьшать их счётчики ссылок. Однако допускаются экземпляры подклассов строк. Даже если освобождение обычных строк не вызовет обратного вызова в наши объекты, нельзя гарантировать, что освобождение экземпляра подкласса строки не вызовет такого обратного вызова.14241425\[[4](https://python-all.ru/3.15/extending/newtypes_tutorial.html#id4)\]14261427Кроме того, даже если атрибуты ограничены экземплярами строк, пользователь может передать произвольные подклассы [`str`](https://python-all.ru/3.15/library/stdtypes.html#str) и тем самым всё равно создать циклические ссылки.1428