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

newtypes_tutorial.md

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

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