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

newtypes_tutorial.md

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

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