newtypes_tutorial.md
1> **Источник:** https://python-all.ru/3.12/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.12/library/stdtypes.html#str) и [`list`](https://python-all.ru/3.12/library/stdtypes.html#list). Код для всех типов расширений следует определённому шаблону, но есть некоторые детали, которые необходимо понять, прежде чем приступить к работе. Этот документ является вводным руководством по данной теме.1011## 2.1. Основы1213Среда выполнения [CPython](https://python-all.ru/3.12/glossary.html#term-CPython) видит все объекты Python как переменные типа [PyObject](https://python-all.ru/3.12/c-api/structures.html#c.PyObject)\*, который служит «базовым типом» для всех объектов Python. Сама структура [`PyObject`](https://python-all.ru/3.12/c-api/structures.html#c.PyObject) содержит только [счётчик ссылок](https://python-all.ru/3.12/glossary.html#term-reference-count) объекта и указатель на его «объект типа». В этом и заключается суть: объект типа определяет, какие (C) функции вызывает интерпретатор, когда, например, выполняется поиск атрибута у объекта, вызов метода или умножение на другой объект. Эти C-функции называются «методами типа».1415Итак, чтобы определить новый тип расширения, необходимо создать новый объект типа.1617Такие вещи можно объяснить только на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип с именем `Custom` внутри C-модуля расширения `custom`:1819> **Примечание**20>21> То, что мы показываем здесь, – это традиционный способ определения *статических* типов расширений. Он должен быть достаточным для большинства случаев. C API также позволяет определять типы расширений, размещаемые в куче, с помощью функции [`PyType_FromSpec()`](https://python-all.ru/3.12/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 PyModuleDef custommodule = {43 .m_base = PyModuleDef_HEAD_INIT,44 .m_name = "custom",45 .m_doc = "Example module that creates an extension type.",46 .m_size = -1,47};4849PyMODINIT_FUNC50PyInit_custom(void)51{52 PyObject *m;53 if (PyType_Ready(&CustomType) < 0)54 return NULL;5556 m = PyModule_Create(&custommodule);57 if (m == NULL)58 return NULL;5960 Py_INCREF(&CustomType);61 if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {62 Py_DECREF(&CustomType);63 Py_DECREF(m);64 return NULL;65 }6667 return m;68}69```7071Это довольно много для одномоментного восприятия, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы. Этот файл определяет три вещи:72731. Что содержит `Custom` **объект**: это структура `CustomObject`, которая выделяется один раз для каждого экземпляра `Custom`.742. Как ведёт себя `Custom` **тип**: это структура `CustomType`, которая определяет набор флагов и указателей на функции, которые интерпретатор проверяет при запросе определённых операций.753. Как инициализировать модуль `custom`: это `PyInit_custom` функция и связанная с ней структура `custommodule`.7677Первая часть:7879```c80typedef struct {81 PyObject_HEAD82} CustomObject;83```8485Вот что будет содержать объект Custom. `PyObject_HEAD` является обязательным в начале каждой структуры объекта и определяет поле с именем `ob_base` типа [`PyObject`](https://python-all.ru/3.12/c-api/structures.html#c.PyObject), содержащее указатель на объект типа и счётчик ссылок (к ним можно получить доступ с помощью макросов [`Py_TYPE`](https://python-all.ru/3.12/c-api/structures.html#c.Py_TYPE) и [`Py_REFCNT`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_REFCNT) соответственно). Смысл макроса – абстрагироваться от расположения полей и разрешить дополнительные поля в [отладочных сборках](https://python-all.ru/3.12/using/configure.html#debug-build).8687> **Примечание**88>89> В приведённом выше коде нет точки с запятой после макроса [`PyObject_HEAD`](https://python-all.ru/3.12/c-api/structures.html#c.PyObject_HEAD). Будьте осторожны, чтобы случайно её не добавить: некоторые компиляторы выдадут ошибку.9091Конечно, объекты обычно хранят дополнительные данные помимо стандартного шаблона `PyObject_HEAD`; например, вот определение для стандартных чисел с плавающей запятой Python:9293```c94typedef struct {95 PyObject_HEAD96 double ob_fval;97} PyFloatObject;98```99100Вторая часть – это определение объекта типа.101102```c103static PyTypeObject CustomType = {104 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)105 .tp_name = "custom.Custom",106 .tp_doc = PyDoc_STR("Custom objects"),107 .tp_basicsize = sizeof(CustomObject),108 .tp_itemsize = 0,109 .tp_flags = Py_TPFLAGS_DEFAULT,110 .tp_new = PyType_GenericNew,111};112```113114> **Примечание**115>116> Рекомендуется использовать именованные инициализаторы в стиле C99, как указано выше, чтобы не перечислять все поля [`PyTypeObject`](https://python-all.ru/3.12/c-api/type.html#c.PyTypeObject), которые не важны, а также не беспокоиться о порядке объявления полей.117118Фактическое определение [`PyTypeObject`](https://python-all.ru/3.12/c-api/type.html#c.PyTypeObject) в `object.h` содержит гораздо больше [полей](https://python-all.ru/3.12/c-api/typeobj.html#type-structs), чем приведённое выше определение. Оставшиеся поля будут заполнены нулями компилятором C, и обычно принято не указывать их явно, если они не нужны.119120Мы разберём его, поле за полем:121122```c123.ob_base = PyVarObject_HEAD_INIT(NULL, 0)124```125126Эта строка является обязательным шаблоном для инициализации поля `ob_base`, упомянутого выше.127128```c129.tp_name = "custom.Custom",130```131132Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:133134```pycon135>>> "" + custom.Custom()136Traceback (most recent call last):137 File "<stdin>", line 1, in <module>138TypeError: can only concatenate str (not "custom.Custom") to str139```140141Обратите внимание, что имя является составным (с точками) и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `custom`, а тип – `Custom`, поэтому мы устанавливаем имя типа `custom.Custom`. Использование реального составного пути импорта важно для совместимости вашего типа с модулями [`pydoc`](https://python-all.ru/3.12/library/pydoc.html#module-pydoc) и [`pickle`](https://python-all.ru/3.12/library/pickle.html#module-pickle).142143```c144.tp_basicsize = sizeof(CustomObject),145.tp_itemsize = 0,146```147148Это нужно, чтобы Python знал, сколько памяти выделять при создании новых экземпляров `Custom`. [`tp_itemsize`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_itemsize) используется только для объектов переменного размера, в остальных случаях должно быть равно нулю.149150> **Примечание**151>152> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.12/reference/datamodel.html#type.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для [`tp_basicsize`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.12/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.153154Мы устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.12/c-api/typeobj.html#c.Py_TPFLAGS_DEFAULT).155156```c157.tp_flags = Py_TPFLAGS_DEFAULT,158```159160Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.161162Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_doc).163164```c165.tp_doc = PyDoc_STR("Custom objects"),166```167168Чтобы разрешить создание объектов, мы должны предоставить обработчик [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new). Это эквивалент метода Python [`__new__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__new__), но должен быть указан явно. В данном случае мы можем просто использовать реализацию по умолчанию, предоставляемую API-функцией [`PyType_GenericNew()`](https://python-all.ru/3.12/c-api/type.html#c.PyType_GenericNew).169170```c171.tp_new = PyType_GenericNew,172```173174Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_custom()`:175176```c177if (PyType_Ready(&CustomType) < 0)178 return;179```180181Это инициализирует тип `Custom`, заполняя ряд членов подходящими значениями по умолчанию, включая [`ob_type`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyObject.ob_type), который мы изначально установили в `NULL`.182183```c184Py_INCREF(&CustomType);185if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {186 Py_DECREF(&CustomType);187 Py_DECREF(m);188 return NULL;189}190```191192Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Custom` вызовом класса `Custom`:193194```pycon195>>> import custom196>>> mycustom = custom.Custom()197```198199Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `custom.c`,200201```c202[build-system]203requires = ["setuptools"]204build-backend = "setuptools.build_meta"205206[project]207name = "custom"208version = "1"209```210211в файл с именем `pyproject.toml`, и212213```python214from setuptools import Extension, setup215setup(ext_modules=[Extension("custom", ["custom.c"])])216```217218в файл с именем `setup.py`; затем введите219220```console221$ python -m pip install .222```223224в оболочке должен создать файл `custom.so` в подкаталоге и установить его; теперь запустите Python – вы сможете `import custom` и поиграть с объектами `Custom`.225226Было не так сложно, правда?227228Конечно, текущий тип Custom довольно неинтересен. У него нет данных, и он ничего не делает. Его даже нельзя унаследовать.229230## 2.2. Добавление данных и методов к базовому примеру231232Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `custom2`, который добавляет эти возможности:233234```c235#define PY_SSIZE_T_CLEAN236#include <Python.h>237#include <stddef.h> /* для offsetof() */238239typedef struct {240 PyObject_HEAD241 PyObject *first; /* имя */242 PyObject *last; /* фамилия */243 int number;244} CustomObject;245246static void247Custom_dealloc(CustomObject *self)248{249 Py_XDECREF(self->first);250 Py_XDECREF(self->last);251 Py_TYPE(self)->tp_free((PyObject *) self);252}253254static PyObject *255Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)256{257 CustomObject *self;258 self = (CustomObject *) type->tp_alloc(type, 0);259 if (self != NULL) {260 self->first = PyUnicode_FromString("");261 if (self->first == NULL) {262 Py_DECREF(self);263 return NULL;264 }265 self->last = PyUnicode_FromString("");266 if (self->last == NULL) {267 Py_DECREF(self);268 return NULL;269 }270 self->number = 0;271 }272 return (PyObject *) self;273}274275static int276Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)277{278 static char *kwlist[] = {"first", "last", "number", NULL};279 PyObject *first = NULL, *last = NULL;280281 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,282 &first, &last,283 &self->number))284 return -1;285286 if (first) {287 Py_XSETREF(self->first, Py_NewRef(first));288 }289 if (last) {290 Py_XSETREF(self->last, Py_NewRef(last));291 }292 return 0;293}294295static PyMemberDef Custom_members[] = {296 {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,297 "first name"},298 {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,299 "last name"},300 {"number", Py_T_INT, offsetof(CustomObject, number), 0,301 "custom number"},302 {NULL} /* Страж */303};304305static PyObject *306Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))307{308 if (self->first == NULL) {309 PyErr_SetString(PyExc_AttributeError, "first");310 return NULL;311 }312 if (self->last == NULL) {313 PyErr_SetString(PyExc_AttributeError, "last");314 return NULL;315 }316 return PyUnicode_FromFormat("%S %S", self->first, self->last);317}318319static PyMethodDef Custom_methods[] = {320 {"name", (PyCFunction) Custom_name, METH_NOARGS,321 "Return the name, combining the first and last name"322 },323 {NULL} /* Страж */324};325326static PyTypeObject CustomType = {327 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)328 .tp_name = "custom2.Custom",329 .tp_doc = PyDoc_STR("Custom objects"),330 .tp_basicsize = sizeof(CustomObject),331 .tp_itemsize = 0,332 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,333 .tp_new = Custom_new,334 .tp_init = (initproc) Custom_init,335 .tp_dealloc = (destructor) Custom_dealloc,336 .tp_members = Custom_members,337 .tp_methods = Custom_methods,338};339340static PyModuleDef custommodule = {341 .m_base =PyModuleDef_HEAD_INIT,342 .m_name = "custom2",343 .m_doc = "Example module that creates an extension type.",344 .m_size = -1,345};346347PyMODINIT_FUNC348PyInit_custom2(void)349{350 PyObject *m;351 if (PyType_Ready(&CustomType) < 0)352 return NULL;353354 m = PyModule_Create(&custommodule);355 if (m == NULL)356 return NULL;357358 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {359 Py_DECREF(m);360 return NULL;361 }362363 return m;364}365```366367Эта версия модуля содержит ряд изменений.368369Тип `Custom` теперь имеет три атрибута данных в своей C-структуре: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – это целое число C.370371Структура объекта обновляется соответствующим образом:372373```c374typedef struct {375 PyObject_HEAD376 PyObject *first; /* имя */377 PyObject *last; /* фамилия */378 int number;379} CustomObject;380```381382Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:383384```c385static void386Custom_dealloc(CustomObject *self)387{388 Py_XDECREF(self->first);389 Py_XDECREF(self->last);390 Py_TYPE(self)->tp_free((PyObject *) self);391}392```393394который присваивается члену [`tp_dealloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):395396```c397.tp_dealloc = (destructor) Custom_dealloc,398```399400Этот метод сначала сбрасывает счётчики ссылок двух атрибутов Python. [`Py_XDECREF()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_XDECREF) корректно обрабатывает случай, когда его аргумент равен `NULL` (что может произойти, если `tp_new` не выполнился до конца). Затем он вызывает член [`tp_free`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта (вычисленный с помощью `Py_TYPE(self)`) для освобождения памяти объекта. Обратите внимание, что тип объекта может не быть `CustomType`, поскольку объект может быть экземпляром подкласса.401402> **Примечание**403>404> Явное приведение к `destructor` выше необходимо, потому что мы определили `Custom_dealloc` как принимающий аргумент `CustomObject *`, но указатель на функцию `tp_dealloc` ожидает получения аргумента `PyObject *`. В противном случае компилятор выдаст предупреждение. Это объектно-ориентированный полиморфизм в C!405406Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому мы предоставляем реализацию `tp_new`:407408```c409static PyObject *410Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)411{412 CustomObject *self;413 self = (CustomObject *) type->tp_alloc(type, 0);414 if (self != NULL) {415 self->first = PyUnicode_FromString("");416 if (self->first == NULL) {417 Py_DECREF(self);418 return NULL;419 }420 self->last = PyUnicode_FromString("");421 if (self->last == NULL) {422 Py_DECREF(self);423 return NULL;424 }425 self->number = 0;426 }427 return (PyObject *) self;428}429```430431и устанавливаем его в член [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new):432433```c434.tp_new = Custom_new,435```436437Обработчик `tp_new` отвечает за создание (в отличие от инициализации) объектов типа. Он предоставляется в Python как метод [`__new__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__new__). Не требуется определять член `tp_new`, и действительно, многие расширения типов просто повторно используют [`PyType_GenericNew()`](https://python-all.ru/3.12/c-api/type.html#c.PyType_GenericNew), как это сделано в первой версии типа `Custom` выше. В этом случае мы используем обработчик `tp_new` для инициализации атрибутов `first` и `last` значениями по умолчанию, отличными от `NULL`.438439`tp_new` передаётся тип, экземпляр которого создаётся (не обязательно `CustomType`, если создаётся экземпляр подкласса), и любые аргументы, переданные при вызове типа; ожидается, что он вернёт созданный экземпляр. Обработчики `tp_new` всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам (также известным как `tp_init` в C или `__init__` в Python).440441> **Примечание**442>443> `tp_new` не должен вызывать `tp_init` явно, так как интерпретатор сделает это сам.444445Реализация `tp_new` вызывает слот [`tp_alloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти:446447```c448self = (CustomObject *) type->tp_alloc(type, 0);449```450451Поскольку выделение памяти может завершиться неудачей, мы должны проверить результат [`tp_alloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_alloc) на `NULL` перед продолжением.452453> **Примечание**454>455> Мы не заполняли слот [`tp_alloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.12/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.12/library/functions.html#object). Большинство типов используют стратегию выделения по умолчанию.456457> **Примечание**458>459> При создании кооперативного [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new) (такого, который вызывает [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new) или [`__new__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__new__) базового типа) нельзя *не* пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте тип, который будете вызывать, и вызывайте его [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуются от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.12/library/exceptions.html#TypeError).)460461Мы также определяем функцию инициализации, которая принимает аргументы для предоставления начальных значений экземпляру:462463```c464static int465Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)466{467 static char *kwlist[] = {"first", "last", "number", NULL};468 PyObject *first = NULL, *last = NULL, *tmp;469470 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,471 &first, &last,472 &self->number))473 return -1;474475 if (first) {476 tmp = self->first;477 Py_INCREF(first);478 self->first = first;479 Py_XDECREF(tmp);480 }481 if (last) {482 tmp = self->last;483 Py_INCREF(last);484 self->last = last;485 Py_XDECREF(tmp);486 }487 return 0;488}489```490491заполняя слот [`tp_init`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_init).492493```c494.tp_init = (initproc) Custom_init,495```496497Слот [`tp_init`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_init) открыт в Python как метод [`__init__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. Инициализаторы всегда принимают позиционные и именованные аргументы и должны возвращать либо `0` в случае успеха, либо `-1` в случае ошибки.498499В отличие от обработчика `tp_new`, нет никакой гарантии, что `tp_init` будет вызван (например, модуль [`pickle`](https://python-all.ru/3.12/library/pickle.html#module-pickle) по умолчанию не вызывает [`__init__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__init__) для распакованных экземпляров). Он также может быть вызван несколько раз. Кто угодно может вызвать метод `__init__()` на наших объектах. По этой причине нужно быть особенно осторожным при присвоении новых значений атрибутов. Может возникнуть соблазн, например, присвоить члену `first` такое значение:500501```c502if (first) {503 Py_XDECREF(self->first);504 Py_INCREF(first);505 self->first = first;506}507```508509But 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.12/glossary.html#term-GIL) and let arbitrary code run in other threads that accesses and modifies our object.510511Чтобы быть параноидальными и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?512513- когда мы точно знаем, что счётчик ссылок больше 1;514- when we know that deallocation of the object [\[1\]](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id5) will neither release the [GIL](https://python-all.ru/3.12/glossary.html#term-GIL) nor cause any calls back into our type’s code;515- когда уменьшается счётчик ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) для типа, который не поддерживает циклическую сборку мусора [\[2\]](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id6).516517Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:518519```c520static PyMemberDef Custom_members[] = {521 {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,522 "first name"},523 {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,524 "last name"},525 {"number", Py_T_INT, offsetof(CustomObject, number), 0,526 "custom number"},527 {NULL} /* Страж */528};529```530531и поместить определения в слот [`tp_members`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_members):532533```c534.tp_members = Custom_members,535```536537Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Общее управление атрибутами](https://python-all.ru/3.12/extending/newtypes.html#generic-attribute-management) ниже.538539Недостаток этого подхода в том, что он не даёт способа ограничить типы объектов, которые можно присваивать атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Кроме того, атрибуты можно удалить, установив указатели C в `NULL`. Даже если мы можем гарантировать, что члены инициализируются значениями, отличными от `NULL`, члены могут быть установлены в `NULL`, если атрибуты удалены.540541Мы определяем один метод `Custom.name()`, который выводит имя объекта как конкатенацию имени и фамилии.542543```c544static PyObject *545Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))546{547 if (self->first == NULL) {548 PyErr_SetString(PyExc_AttributeError, "first");549 return NULL;550 }551 if (self->last == NULL) {552 PyErr_SetString(PyExc_AttributeError, "last");553 return NULL;554 }555 return PyUnicode_FromFormat("%S %S", self->first, self->last);556}557```558559Метод реализован как функция на C, которая принимает экземпляр `Custom` (или подкласс `Custom`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:560561```python562def name(self):563 return "%s %s" % (self.first, self.last)564```565566Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны `NULL`. Это потому, что их можно удалить, и в этом случае они устанавливаются в `NULL`. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Как это сделать, мы увидим в следующем разделе.567568Теперь, когда мы определили метод, нужно создать массив определений методов:569570```c571static PyMethodDef Custom_methods[] = {572 {"name", (PyCFunction) Custom_name, METH_NOARGS,573 "Return the name, combining the first and last name"574 },575 {NULL} /* Страж */576};577```578579(обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.12/c-api/structures.html#c.METH_NOARGS), чтобы указать, что метод не ожидает аргументов, кроме *self*)580581и назначить его слоту [`tp_methods`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_methods):582583```c584.tp_methods = Custom_methods,585```586587Наконец, сделаем наш тип пригодным для использования в качестве базового класса для создания подклассов. Мы написали наши методы достаточно осторожно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.12/c-api/typeobj.html#c.Py_TPFLAGS_BASETYPE) в определение флага нашего класса:588589```c590.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,591```592593Мы переименовываем `PyInit_custom()` в `PyInit_custom2()`, обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.12/c-api/module.html#c.PyModuleDef) и обновляем полное имя класса в структуре [`PyTypeObject`](https://python-all.ru/3.12/c-api/type.html#c.PyTypeObject).594595Наконец, обновляем файл `setup.py`, чтобы включить новый модуль,596597```python598from setuptools import Extension, setup599setup(ext_modules=[600 Extension("custom", ["custom.c"]),601 Extension("custom2", ["custom2.c"]),602])603```604605а затем переустанавливаем, чтобы можно было `import custom2`:606607```console608$ python -m pip install .609```610611## 2.3. Предоставление более точного управления атрибутами данных612613В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Custom`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.614615```c616#define PY_SSIZE_T_CLEAN617#include <Python.h>618#include <stddef.h> /* для offsetof() */619620typedef struct {621 PyObject_HEAD622 PyObject *first; /* имя */623 PyObject *last; /* фамилия */624 int number;625} CustomObject;626627static void628Custom_dealloc(CustomObject *self)629{630 Py_XDECREF(self->first);631 Py_XDECREF(self->last);632 Py_TYPE(self)->tp_free((PyObject *) self);633}634635static PyObject *636Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)637{638 CustomObject *self;639 self = (CustomObject *) type->tp_alloc(type, 0);640 if (self != NULL) {641 self->first = PyUnicode_FromString("");642 if (self->first == NULL) {643 Py_DECREF(self);644 return NULL;645 }646 self->last = PyUnicode_FromString("");647 if (self->last == NULL) {648 Py_DECREF(self);649 return NULL;650 }651 self->number = 0;652 }653 return (PyObject *) self;654}655656static int657Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)658{659 static char *kwlist[] = {"first", "last", "number", NULL};660 PyObject *first = NULL, *last = NULL;661662 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,663 &first, &last,664 &self->number))665 return -1;666667 if (first) {668 Py_SETREF(self->first, Py_NewRef(first));669 }670 if (last) {671 Py_SETREF(self->last, Py_NewRef(last));672 }673 return 0;674}675676static PyMemberDef Custom_members[] = {677 {"number", Py_T_INT, offsetof(CustomObject, number), 0,678 "custom number"},679 {NULL} /* Страж */680};681682static PyObject *683Custom_getfirst(CustomObject *self, void *closure)684{685 return Py_NewRef(self->first);686}687688static int689Custom_setfirst(CustomObject *self, PyObject *value, void *closure)690{691 if (value == NULL) {692 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");693 return -1;694 }695 if (!PyUnicode_Check(value)) {696 PyErr_SetString(PyExc_TypeError,697 "The first attribute value must be a string");698 return -1;699 }700 Py_SETREF(self->first, Py_NewRef(value));701 return 0;702}703704static PyObject *705Custom_getlast(CustomObject *self, void *closure)706{707 return Py_NewRef(self->last);708}709710static int711Custom_setlast(CustomObject *self, PyObject *value, void *closure)712{713 if (value == NULL) {714 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");715 return -1;716 }717 if (!PyUnicode_Check(value)) {718 PyErr_SetString(PyExc_TypeError,719 "The last attribute value must be a string");720 return -1;721 }722 Py_SETREF(self->last, Py_NewRef(value));723 return 0;724}725726static PyGetSetDef Custom_getsetters[] = {727 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,728 "first name", NULL},729 {"last", (getter) Custom_getlast, (setter) Custom_setlast,730 "last name", NULL},731 {NULL} /* Страж */732};733734static PyObject *735Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))736{737 return PyUnicode_FromFormat("%S %S", self->first, self->last);738}739740static PyMethodDef Custom_methods[] = {741 {"name", (PyCFunction) Custom_name, METH_NOARGS,742 "Return the name, combining the first and last name"743 },744 {NULL} /* Страж */745};746747static PyTypeObject CustomType = {748 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)749 .tp_name = "custom3.Custom",750 .tp_doc = PyDoc_STR("Custom objects"),751 .tp_basicsize = sizeof(CustomObject),752 .tp_itemsize = 0,753 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,754 .tp_new = Custom_new,755 .tp_init = (initproc) Custom_init,756 .tp_dealloc = (destructor) Custom_dealloc,757 .tp_members = Custom_members,758 .tp_methods = Custom_methods,759 .tp_getset = Custom_getsetters,760};761762static PyModuleDef custommodule = {763 .m_base = PyModuleDef_HEAD_INIT,764 .m_name = "custom3",765 .m_doc = "Example module that creates an extension type.",766 .m_size = -1,767};768769PyMODINIT_FUNC770PyInit_custom3(void)771{772 PyObject *m;773 if (PyType_Ready(&CustomType) < 0)774 return NULL;775776 m = PyModule_Create(&custommodule);777 if (m == NULL)778 return NULL;779780 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {781 Py_DECREF(m);782 return NULL;783 }784785 return m;786}787```788789Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:790791```c792static PyObject *793Custom_getfirst(CustomObject *self, void *closure)794{795 Py_INCREF(self->first);796 return self->first;797}798799static int800Custom_setfirst(CustomObject *self, PyObject *value, void *closure)801{802 PyObject *tmp;803 if (value == NULL) {804 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");805 return -1;806 }807 if (!PyUnicode_Check(value)) {808 PyErr_SetString(PyExc_TypeError,809 "The first attribute value must be a string");810 return -1;811 }812 tmp = self->first;813 Py_INCREF(value);814 self->first = value;815 Py_DECREF(tmp);816 return 0;817}818```819820Функция получения принимает объект `Custom` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это может быть использовано, например, для создания единого набора функций получения и установки, которые решают, какой атрибут получить или установить на основе данных в замыкании.)821822Функция установки принимает объект `Custom`, новое значение и замыкание. Новое значение может быть `NULL`, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или его новое значение не является строкой.823824Мы создаём массив структур [`PyGetSetDef`](https://python-all.ru/3.12/c-api/structures.html#c.PyGetSetDef):825826```c827static PyGetSetDef Custom_getsetters[] = {828 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,829 "first name", NULL},830 {"last", (getter) Custom_getlast, (setter) Custom_setlast,831 "last name", NULL},832 {NULL} /* Страж */833};834```835836и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_getset):837838```c839.tp_getset = Custom_getsetters,840```841842Последний элемент структуры [`PyGetSetDef`](https://python-all.ru/3.12/c-api/structures.html#c.PyGetSetDef) – это «замыкание», упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём `NULL`.843844Также удаляем определения членов для этих атрибутов:845846```c847static PyMemberDef Custom_members[] = {848 {"number", Py_T_INT, offsetof(CustomObject, number), 0,849 "custom number"},850 {NULL} /* Страж */851};852```853854Также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id7):855856```c857static int858Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)859{860 static char *kwlist[] = {"first", "last", "number", NULL};861 PyObject *first = NULL, *last = NULL, *tmp;862863 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,864 &first, &last,865 &self->number))866 return -1;867868 if (first) {869 tmp = self->first;870 Py_INCREF(first);871 self->first = first;872 Py_DECREF(tmp);873 }874 if (last) {875 tmp = self->last;876 Py_INCREF(last);877 self->last = last;878 Py_DECREF(tmp);879 }880 return 0;881}882```883884Благодаря этим изменениям можно гарантировать, что члены `first` и `last` никогда не будут `NULL`, поэтому проверки на значения `NULL` можно убрать почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_DECREF). Единственное место, где эти вызовы нельзя изменить, – реализация `tp_dealloc`, где возможна ситуация, что инициализация этих членов завершилась неудачей в `tp_new`.885886Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.887888## 2.4. Поддержка циклической сборки мусора889890Python имеет [циклический сборщик мусора (GC)](https://python-all.ru/3.12/glossary.html#term-garbage-collection), который может определять ненужные объекты даже при ненулевом счётчике ссылок. Это возможно, когда объекты образуют циклы. Например, рассмотрим:891892```pycon893>>> l = []894>>> l.append(l)895>>> del l896```897898В этом примере создаётся список, содержащий сам себя. При его удалении у него всё ещё остаётся ссылка на самого себя. Счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.899900Во второй версии примера `Custom` мы разрешили хранить объекты любого типа в атрибутах `first` или `last` [\[4\]](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id8). Кроме того, во второй и третьей версиях мы разрешили наследование от `Custom`, а подклассы могут добавлять произвольные атрибуты. По любой из этих двух причин объекты `Custom` могут участвовать в циклах:901902```pycon903>>> import custom3904>>> class Derived(custom3.Custom): pass905...906>>> n = Derived()907>>> n.some_attribute = n908```909910Чтобы экземпляр `Custom`, участвующий в цикле ссылок, был корректно обнаружен и собран циклическим GC, наш тип `Custom` должен заполнить два дополнительных слота и включить флаг, активирующий эти слоты:911912```c913#define PY_SSIZE_T_CLEAN914#include <Python.h>915#include <stddef.h> /* для offsetof() */916917typedef struct {918 PyObject_HEAD919 PyObject *first; /* имя */920 PyObject *last; /* фамилия */921 int number;922} CustomObject;923924static int925Custom_traverse(CustomObject *self, visitproc visit, void *arg)926{927 Py_VISIT(self->first);928 Py_VISIT(self->last);929 return 0;930}931932static int933Custom_clear(CustomObject *self)934{935 Py_CLEAR(self->first);936 Py_CLEAR(self->last);937 return 0;938}939940static void941Custom_dealloc(CustomObject *self)942{943 PyObject_GC_UnTrack(self);944 Custom_clear(self);945 Py_TYPE(self)->tp_free((PyObject *) self);946}947948static PyObject *949Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)950{951 CustomObject *self;952 self = (CustomObject *) type->tp_alloc(type, 0);953 if (self != NULL) {954 self->first = PyUnicode_FromString("");955 if (self->first == NULL) {956 Py_DECREF(self);957 return NULL;958 }959 self->last = PyUnicode_FromString("");960 if (self->last == NULL) {961 Py_DECREF(self);962 return NULL;963 }964 self->number = 0;965 }966 return (PyObject *) self;967}968969static int970Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)971{972 static char *kwlist[] = {"first", "last", "number", NULL};973 PyObject *first = NULL, *last = NULL;974975 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,976 &first, &last,977 &self->number))978 return -1;979980 if (first) {981 Py_SETREF(self->first, Py_NewRef(first));982 }983 if (last) {984 Py_SETREF(self->last, Py_NewRef(last));985 }986 return 0;987}988989static PyMemberDef Custom_members[] = {990 {"number", Py_T_INT, offsetof(CustomObject, number), 0,991 "custom number"},992 {NULL} /* Страж */993};994995static PyObject *996Custom_getfirst(CustomObject *self, void *closure)997{998 return Py_NewRef(self->first);999}10001001static int1002Custom_setfirst(CustomObject *self, PyObject *value, void *closure)1003{1004 if (value == NULL) {1005 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");1006 return -1;1007 }1008 if (!PyUnicode_Check(value)) {1009 PyErr_SetString(PyExc_TypeError,1010 "The first attribute value must be a string");1011 return -1;1012 }1013 Py_XSETREF(self->first, Py_NewRef(value));1014 return 0;1015}10161017static PyObject *1018Custom_getlast(CustomObject *self, void *closure)1019{1020 return Py_NewRef(self->last);1021}10221023static int1024Custom_setlast(CustomObject *self, PyObject *value, void *closure)1025{1026 if (value == NULL) {1027 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");1028 return -1;1029 }1030 if (!PyUnicode_Check(value)) {1031 PyErr_SetString(PyExc_TypeError,1032 "The last attribute value must be a string");1033 return -1;1034 }1035 Py_XSETREF(self->last, Py_NewRef(value));1036 return 0;1037}10381039static PyGetSetDef Custom_getsetters[] = {1040 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,1041 "first name", NULL},1042 {"last", (getter) Custom_getlast, (setter) Custom_setlast,1043 "last name", NULL},1044 {NULL} /* Страж */1045};10461047static PyObject *1048Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))1049{1050 return PyUnicode_FromFormat("%S %S", self->first, self->last);1051}10521053static PyMethodDef Custom_methods[] = {1054 {"name", (PyCFunction) Custom_name, METH_NOARGS,1055 "Return the name, combining the first and last name"1056 },1057 {NULL} /* Страж */1058};10591060static PyTypeObject CustomType = {1061 .ob_base = PyVarObject_HEAD_INIT(NULL, 0)1062 .tp_name = "custom4.Custom",1063 .tp_doc = PyDoc_STR("Custom objects"),1064 .tp_basicsize = sizeof(CustomObject),1065 .tp_itemsize = 0,1066 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1067 .tp_new = Custom_new,1068 .tp_init = (initproc) Custom_init,1069 .tp_dealloc = (destructor) Custom_dealloc,1070 .tp_traverse = (traverseproc) Custom_traverse,1071 .tp_clear = (inquiry) Custom_clear,1072 .tp_members = Custom_members,1073 .tp_methods = Custom_methods,1074 .tp_getset = Custom_getsetters,1075};10761077static PyModuleDef custommodule = {1078 .m_base = PyModuleDef_HEAD_INIT,1079 .m_name = "custom4",1080 .m_doc = "Example module that creates an extension type.",1081 .m_size = -1,1082};10831084PyMODINIT_FUNC1085PyInit_custom4(void)1086{1087 PyObject *m;1088 if (PyType_Ready(&CustomType) < 0)1089 return NULL;10901091 m = PyModule_Create(&custommodule);1092 if (m == NULL)1093 return NULL;10941095 if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {1096 Py_DECREF(m);1097 return NULL;1098 }10991100 return m;1101}1102```11031104Во-первых, метод обхода (traversal) сообщает циклическому GC о подобъектах, которые могут участвовать в циклах:11051106```c1107static int1108Custom_traverse(CustomObject *self, visitproc visit, void *arg)1109{1110 int vret;1111 if (self->first) {1112 vret = visit(self->first, arg);1113 if (vret != 0)1114 return vret;1115 }1116 if (self->last) {1117 vret = visit(self->last, arg);1118 if (vret != 0)1119 return vret;1120 }1121 return 0;1122}1123```11241125Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.11261127Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.12/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.12/c-api/gcsupport.html#c.Py_VISIT) можно минимизировать количество шаблонного кода в `Custom_traverse`:11281129```c1130static int1131Custom_traverse(CustomObject *self, visitproc visit, void *arg)1132{1133 Py_VISIT(self->first);1134 Py_VISIT(self->last);1135 return 0;1136}1137```11381139> **Примечание**1140>1141> Реализация [`tp_traverse`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна именовать свои аргументы именно как *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.12/c-api/gcsupport.html#c.Py_VISIT).11421143Во-вторых, нужно предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах:11441145```c1146static int1147Custom_clear(CustomObject *self)1148{1149 Py_CLEAR(self->first);1150 Py_CLEAR(self->last);1151 return 0;1152}1153```11541155Обратите внимание на использование макроса [`Py_CLEAR()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_CLEAR). Это рекомендуемый и безопасный способ очистки атрибутов данных произвольных типов с одновременным уменьшением их счётчиков ссылок. Если вместо этого вызвать [`Py_XDECREF()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_XDECREF) для атрибута перед установкой его в `NULL`, существует вероятность, что деструктор атрибута повторно вызовет код, читающий этот атрибут (*особенно* при наличии циклической ссылки).11561157> **Примечание**1158>1159> Можно эмулировать [`Py_CLEAR()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_CLEAR) следующим образом:1160>1161> ```c1162> PyObject *tmp;1163> tmp = self->first;1164> self->first = NULL;1165> Py_XDECREF(tmp);1166> ```1167>1168> Тем не менее, гораздо проще и менее чревато ошибками всегда использовать [`Py_CLEAR()`](https://python-all.ru/3.12/c-api/refcounting.html#c.Py_CLEAR) при удалении атрибута. Не пытайтесь микрооптимизировать в ущерб надёжности!11691170Деаллокатор `Custom_dealloc` может вызывать произвольный код при очистке атрибутов. Это означает, что циклический GC может быть запущен внутри функции. Поскольку GC предполагает, что счётчик ссылок не равен нулю, нужно отменить отслеживание объекта GC с помощью вызова [`PyObject_GC_UnTrack()`](https://python-all.ru/3.12/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой членов. Вот наша переработанная версия деаллокатора с использованием [`PyObject_GC_UnTrack()`](https://python-all.ru/3.12/c-api/gcsupport.html#c.PyObject_GC_UnTrack) и `Custom_clear`:11711172```c1173static void1174Custom_dealloc(CustomObject *self)1175{1176 PyObject_GC_UnTrack(self);1177 Custom_clear(self);1178 Py_TYPE(self)->tp_free((PyObject *) self);1179}1180```11811182Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.12/c-api/typeobj.html#c.Py_TPFLAGS_HAVE_GC) в флаги класса:11831184```c1185.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1186```11871188Вот, в общем-то, и всё. Если бы мы написали собственные обработчики [`tp_alloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_free), их пришлось бы доработать для поддержки циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.11891190## 2.5. Наследование от других типов11911192Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему [`PyTypeObject`](https://python-all.ru/3.12/c-api/type.html#c.PyTypeObject). Совместное использование структур [`PyTypeObject`](https://python-all.ru/3.12/c-api/type.html#c.PyTypeObject) между модулями расширений может быть затруднительным.11931194В этом примере мы создадим тип `SubList`, наследующий от встроенного типа [`list`](https://python-all.ru/3.12/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик:11951196```pycon1197>>> import sublist1198>>> s = sublist.SubList(range(3))1199>>> s.extend(s)1200>>> print(len(s))120161202>>> print(s.increment())120311204>>> print(s.increment())120521206```12071208```c1209#define PY_SSIZE_T_CLEAN1210#include <Python.h>12111212typedef struct {1213 PyListObject list;1214 int state;1215} SubListObject;12161217static PyObject *1218SubList_increment(SubListObject *self, PyObject *unused)1219{1220 self->state++;1221 return PyLong_FromLong(self->state);1222}12231224static PyMethodDef SubList_methods[] = {1225 {"increment", (PyCFunction) SubList_increment, METH_NOARGS,1226 PyDoc_STR("increment state counter")},1227 {NULL},1228};12291230static int1231SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1232{1233 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1234 return -1;1235 self->state = 0;1236 return 0;1237}12381239static PyTypeObject SubListType = {1240 PyVarObject_HEAD_INIT(NULL, 0)1241 .tp_name = "sublist.SubList",1242 .tp_doc = PyDoc_STR("SubList objects"),1243 .tp_basicsize = sizeof(SubListObject),1244 .tp_itemsize = 0,1245 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,1246 .tp_init = (initproc) SubList_init,1247 .tp_methods = SubList_methods,1248};12491250static PyModuleDef sublistmodule = {1251 PyModuleDef_HEAD_INIT,1252 .m_name = "sublist",1253 .m_doc = "Example module that creates an extension type.",1254 .m_size = -1,1255};12561257PyMODINIT_FUNC1258PyInit_sublist(void)1259{1260 PyObject *m;1261 SubListType.tp_base = &PyList_Type;1262 if (PyType_Ready(&SubListType) < 0)1263 return NULL;12641265 m = PyModule_Create(&sublistmodule);1266 if (m == NULL)1267 return NULL;12681269 Py_INCREF(&SubListType);1270 if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {1271 Py_DECREF(&SubListType);1272 Py_DECREF(m);1273 return NULL;1274 }12751276 return m;1277}1278```12791280Как видите, исходный код очень похож на примеры `Custom` из предыдущих разделов. Разберём основные различия между ними.12811282```c1283typedef struct {1284 PyListObject list;1285 int state;1286} SubListObject;1287```12881289Основное отличие для объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.12/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.12901291Когда объект Python является экземпляром `SubList`, его указатель `PyObject *` можно безопасно приводить как к `PyListObject *`, так и к `SubListObject *`:12921293```c1294static int1295SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1296{1297 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1298 return -1;1299 self->state = 0;1300 return 0;1301}1302```13031304Выше показано, как вызывать метод [`__init__()`](https://python-all.ru/3.12/reference/datamodel.html#object.__init__) базового типа.13051306Этот шаблон важен при написании типа с пользовательскими членами [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_dealloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Обработчик [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new) не должен фактически выделять память для объекта с помощью своего [`tp_alloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_alloc), а должен позволить базовому классу обработать это, вызвав его собственный [`tp_new`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_new).13071308Структура [`PyTypeObject`](https://python-all.ru/3.12/c-api/type.html#c.PyTypeObject) поддерживает [`tp_base`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_base) указывающее конкретный базовый класс типа. Из-за проблем с кроссплатформенными компиляторами нельзя заполнить это поле напрямую ссылкой на [`PyList_Type`](https://python-all.ru/3.12/c-api/list.html#c.PyList_Type); это следует сделать позже, в функции инициализации модуля:13091310```c1311PyMODINIT_FUNC1312PyInit_sublist(void)1313{1314 PyObject* m;1315 SubListType.tp_base = &PyList_Type;1316 if (PyType_Ready(&SubListType) < 0)1317 return NULL;13181319 m = PyModule_Create(&sublistmodule);1320 if (m == NULL)1321 return NULL;13221323 Py_INCREF(&SubListType);1324 if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {1325 Py_DECREF(&SubListType);1326 Py_DECREF(m);1327 return NULL;1328 }13291330 return m;1331}1332```13331334Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.12/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании от существующего типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.12/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.13351336После этого вызов [`PyType_Ready()`](https://python-all.ru/3.12/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Custom`.13371338Сноски13391340\[[1](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id1)\]13411342Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой.13431344\[[2](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id2)\]13451346На это полагались в обработчике [`tp_dealloc`](https://python-all.ru/3.12/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) в данном примере, поскольку тип не поддерживает сборку мусора.13471348\[[3](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id3)\]13491350Теперь известно, что первый и последний элементы являются строками, поэтому можно было бы менее осторожно уменьшать их счётчики ссылок. Однако допускаются экземпляры подклассов строк. Даже если освобождение обычных строк не вызовет обратного вызова в наши объекты, нельзя гарантировать, что освобождение экземпляра подкласса строки не вызовет такого обратного вызова.13511352\[[4](https://python-all.ru/3.12/extending/newtypes_tutorial.html#id4)\]13531354Кроме того, даже если атрибуты ограничены экземплярами строк, пользователь может передать произвольные подклассы [`str`](https://python-all.ru/3.12/library/stdtypes.html#str) и тем самым всё равно создать циклические ссылки.1355