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

newtypes_tutorial.md

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

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