newtypes_tutorial.md
1> **Источник:** https://python-all.ru/3.8/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.8/library/stdtypes.html#str) и [`list`](https://python-all.ru/3.8/library/stdtypes.html#list). Код для всех типов расширений следует определённому шаблону, но есть некоторые детали, которые необходимо понять, прежде чем приступить к работе. Этот документ является вводным руководством по данной теме.1011## 2.1. Основы1213Среда выполнения [CPython](https://python-all.ru/3.8/glossary.html#term-cpython) рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/3.8/c-api/structures.html#c.PyObject), который служит «базовым типом» для всех объектов Python. Сама структура [`PyObject`](https://python-all.ru/3.8/c-api/structures.html#c.PyObject) содержит только [счётчик ссылок](https://python-all.ru/3.8/glossary.html#term-reference-count) объекта и указатель на «объект типа» объекта. В этом и заключается суть: объект типа определяет, какие (C) функции вызываются интерпретатором, когда, например, выполняется поиск атрибута объекта, вызов метода или его умножение на другой объект. Эти C-функции называются «методами типа».1415Итак, чтобы определить новый тип расширения, необходимо создать новый объект типа.1617Такие вещи можно объяснить только на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип с именем `Custom` внутри C-модуля расширения `custom`:1819> **Примечание**20>21> То, что мы показываем здесь, – это традиционный способ определения *статических* типов расширений. Он должен быть достаточным для большинства случаев. C API также позволяет определять типы расширений, размещаемые в куче, с помощью функции [`PyType_FromSpec()`](https://python-all.ru/3.8/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 PyVarObject_HEAD_INIT(NULL, 0)34 .tp_name = "custom.Custom",35 .tp_doc = "Custom objects",36 .tp_basicsize = sizeof(CustomObject),37 .tp_itemsize = 0,38 .tp_flags = Py_TPFLAGS_DEFAULT,39 .tp_new = PyType_GenericNew,40};4142static PyModuleDef custommodule = {43 PyModuleDef_HEAD_INIT,44 .m_name = "custom",45 .m_doc = "Example module that creates an extension type.",46 .m_size = -1,47};4849PyMODINIT_FUNC50PyInit_custom(void)51{52 PyObject *m;53 if (PyType_Ready(&CustomType) < 0)54 return NULL;5556 m = PyModule_Create(&custommodule);57 if (m == NULL)58 return NULL;5960 Py_INCREF(&CustomType);61 if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {62 Py_DECREF(&CustomType);63 Py_DECREF(m);64 return NULL;65 }6667 return m;68}69```7071Это довольно много для одномоментного восприятия, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы. Этот файл определяет три вещи:72731. Что содержит `Custom` **объект**: это структура `CustomObject`, которая выделяется один раз для каждого экземпляра `Custom`.742. Как ведёт себя `Custom` **тип**: это структура `CustomType`, которая определяет набор флагов и указателей на функции, которые интерпретатор проверяет при запросе определённых операций.753. Как инициализировать модуль `custom`: это `PyInit_custom` функция и связанная с ней структура `custommodule`.7677Первая часть:7879```c80typedef struct {81 PyObject_HEAD82} CustomObject;83```8485Вот что будет содержать пользовательский объект. `PyObject_HEAD` является обязательным в начале каждой структуры объекта и определяет поле `ob_base` типа [`PyObject`](https://python-all.ru/3.8/c-api/structures.html#c.PyObject), содержащее указатель на объект типа и счётчик ссылок (доступ к ним можно получить с помощью макросов [`Py_REFCNT`](https://python-all.ru/3.8/c-api/structures.html#c.Py_REFCNT) и [`Py_TYPE`](https://python-all.ru/3.8/c-api/structures.html#c.Py_TYPE) соответственно). Причина использования макроса – абстрагирование от компоновки и возможность добавления дополнительных полей в отладочных сборках.8687> **Примечание**88>89> В приведённом выше коде нет точки с запятой после макроса [`PyObject_HEAD`](https://python-all.ru/3.8/c-api/structures.html#c.PyObject_HEAD). Будьте осторожны, чтобы случайно её не добавить: некоторые компиляторы выдадут ошибку.9091Конечно, объекты обычно хранят дополнительные данные помимо стандартного шаблона `PyObject_HEAD`; например, вот определение для стандартных чисел с плавающей запятой Python:9293```c94typedef struct {95 PyObject_HEAD96 double ob_fval;97} PyFloatObject;98```99100Вторая часть – это определение объекта типа.101102```c103static PyTypeObject CustomType = {104 PyVarObject_HEAD_INIT(NULL, 0)105 .tp_name = "custom.Custom",106 .tp_doc = "Custom objects",107 .tp_basicsize = sizeof(CustomObject),108 .tp_itemsize = 0,109 .tp_flags = Py_TPFLAGS_DEFAULT,110 .tp_new = PyType_GenericNew,111};112```113114> **Примечание**115>116> Рекомендуется использовать именованные инициализаторы в стиле C99, как указано выше, чтобы не перечислять все поля [`PyTypeObject`](https://python-all.ru/3.8/c-api/type.html#c.PyTypeObject), которые не важны, а также не беспокоиться о порядке объявления полей.117118Фактическое определение [`PyTypeObject`](https://python-all.ru/3.8/c-api/type.html#c.PyTypeObject) в `object.h` содержит гораздо больше [полей](https://python-all.ru/3.8/c-api/typeobj.html#type-structs), чем приведённое выше определение. Оставшиеся поля будут заполнены нулями компилятором C, и обычно принято не указывать их явно, если они не нужны.119120Мы разберём его, поле за полем:121122```c123PyVarObject_HEAD_INIT(NULL, 0)124```125126Эта строка является обязательным шаблоном для инициализации поля `ob_base`, упомянутого выше.127128```c129.tp_name = "custom.Custom",130```131132Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:133134```pycon135>>> "" + custom.Custom()136Traceback (most recent call last):137 File "<stdin>", line 1, in <module>138TypeError: can only concatenate str (not "custom.Custom") to str139```140141Обратите внимание, что имя является составным (с точками) и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `custom`, а тип – `Custom`, поэтому мы устанавливаем имя типа `custom.Custom`. Использование реального составного пути импорта важно для совместимости вашего типа с модулями [`pydoc`](https://python-all.ru/3.8/library/pydoc.html#module-pydoc) и [`pickle`](https://python-all.ru/3.8/library/pickle.html#module-pickle).142143```c144.tp_basicsize = sizeof(CustomObject),145.tp_itemsize = 0,146```147148Это нужно, чтобы Python знал, сколько памяти выделять при создании новых экземпляров `Custom`. [`tp_itemsize`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_itemsize) используется только для объектов переменного размера, в остальных случаях должно быть равно нулю.149150> **Примечание**151>152> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.8/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для [`tp_basicsize`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.8/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.153154Мы устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.8/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).155156```c157.tp_flags = Py_TPFLAGS_DEFAULT,158```159160Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.161162Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_doc).163164```c165.tp_doc = "Custom objects",166```167168Чтобы разрешить создание объектов, мы должны предоставить обработчик [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new). Это эквивалент метода Python [`__new__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__new__), но должен быть указан явно. В данном случае мы можем просто использовать реализацию по умолчанию, предоставляемую API-функцией [`PyType_GenericNew()`](https://python-all.ru/3.8/c-api/type.html#c.PyType_GenericNew).169170```c171.tp_new = PyType_GenericNew,172```173174Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_custom()`:175176```c177if (PyType_Ready(&CustomType) < 0)178 return;179```180181Это инициализирует тип `Custom`, заполняя ряд членов подходящими значениями по умолчанию, включая `ob_type`, который мы изначально установили в `NULL`.182183```c184Py_INCREF(&CustomType);185if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {186 Py_DECREF(&CustomType);187 Py_DECREF(m);188 return NULL;189}190```191192Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Custom` вызовом класса `Custom`:193194```pycon195>>> import custom196>>> mycustom = custom.Custom()197```198199Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `custom.c` и:200201```python202from distutils.core import setup, Extension203setup(name="custom", version="1.0",204 ext_modules=[Extension("custom", ["custom.c"])])205```206207в файл с именем `setup.py`; затем введите208209```console210$ python setup.py build211```212213в командной оболочке должен создать файл `custom.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import custom` и поработать с объектами Custom.214215Было не так сложно, правда?216217Конечно, текущий тип Custom довольно неинтересен. У него нет данных, и он ничего не делает. Его даже нельзя унаследовать.218219> **Примечание**220>221> Хотя в этой документации демонстрируется стандартный модуль [`distutils`](https://python-all.ru/3.8/library/distutils.html#module-distutils) для создания расширений на C, в реальных проектах рекомендуется использовать более новую и лучше поддерживаемую библиотеку `setuptools`. Документация о том, как это сделать, выходит за рамки данного документа и находится в [Руководстве пользователя по упаковке Python](https://python-all.ru/3.8/extending/newtypes_tutorial.html).222223## 2.2. Добавление данных и методов к базовому примеру224225Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `custom2`, который добавляет эти возможности:226227```c228#define PY_SSIZE_T_CLEAN229#include <Python.h>230#include "structmember.h"231232typedef struct {233 PyObject_HEAD234 PyObject *first; /* имя */235 PyObject *last; /* фамилия */236 int number;237} CustomObject;238239static void240Custom_dealloc(CustomObject *self)241{242 Py_XDECREF(self->first);243 Py_XDECREF(self->last);244 Py_TYPE(self)->tp_free((PyObject *) self);245}246247static PyObject *248Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)249{250 CustomObject *self;251 self = (CustomObject *) type->tp_alloc(type, 0);252 if (self != NULL) {253 self->first = PyUnicode_FromString("");254 if (self->first == NULL) {255 Py_DECREF(self);256 return NULL;257 }258 self->last = PyUnicode_FromString("");259 if (self->last == NULL) {260 Py_DECREF(self);261 return NULL;262 }263 self->number = 0;264 }265 return (PyObject *) self;266}267268static int269Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)270{271 static char *kwlist[] = {"first", "last", "number", NULL};272 PyObject *first = NULL, *last = NULL, *tmp;273274 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,275 &first, &last,276 &self->number))277 return -1;278279 if (first) {280 tmp = self->first;281 Py_INCREF(first);282 self->first = first;283 Py_XDECREF(tmp);284 }285 if (last) {286 tmp = self->last;287 Py_INCREF(last);288 self->last = last;289 Py_XDECREF(tmp);290 }291 return 0;292}293294static PyMemberDef Custom_members[] = {295 {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,296 "first name"},297 {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,298 "last name"},299 {"number", T_INT, offsetof(CustomObject, number), 0,300 "custom number"},301 {NULL} /* Страж */302};303304static PyObject *305Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))306{307 if (self->first == NULL) {308 PyErr_SetString(PyExc_AttributeError, "first");309 return NULL;310 }311 if (self->last == NULL) {312 PyErr_SetString(PyExc_AttributeError, "last");313 return NULL;314 }315 return PyUnicode_FromFormat("%S %S", self->first, self->last);316}317318static PyMethodDef Custom_methods[] = {319 {"name", (PyCFunction) Custom_name, METH_NOARGS,320 "Return the name, combining the first and last name"321 },322 {NULL} /* Страж */323};324325static PyTypeObject CustomType = {326 PyVarObject_HEAD_INIT(NULL, 0)327 .tp_name = "custom2.Custom",328 .tp_doc = "Custom objects",329 .tp_basicsize = sizeof(CustomObject),330 .tp_itemsize = 0,331 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,332 .tp_new = Custom_new,333 .tp_init = (initproc) Custom_init,334 .tp_dealloc = (destructor) Custom_dealloc,335 .tp_members = Custom_members,336 .tp_methods = Custom_methods,337};338339static PyModuleDef custommodule = {340 PyModuleDef_HEAD_INIT,341 .m_name = "custom2",342 .m_doc = "Example module that creates an extension type.",343 .m_size = -1,344};345346PyMODINIT_FUNC347PyInit_custom2(void)348{349 PyObject *m;350 if (PyType_Ready(&CustomType) < 0)351 return NULL;352353 m = PyModule_Create(&custommodule);354 if (m == NULL)355 return NULL;356357 Py_INCREF(&CustomType);358 if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {359 Py_DECREF(&CustomType);360 Py_DECREF(m);361 return NULL;362 }363364 return m;365}366```367368Эта версия модуля содержит ряд изменений.369370Мы добавили дополнительный include:371372```c373#include <structmember.h>374```375376Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.377378Тип `Custom` теперь имеет три атрибута данных в своей C-структуре: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – это целое число C.379380Структура объекта обновляется соответствующим образом:381382```c383typedef struct {384 PyObject_HEAD385 PyObject *first; /* имя */386 PyObject *last; /* фамилия */387 int number;388} CustomObject;389```390391Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:392393```c394static void395Custom_dealloc(CustomObject *self)396{397 Py_XDECREF(self->first);398 Py_XDECREF(self->last);399 Py_TYPE(self)->tp_free((PyObject *) self);400}401```402403который присваивается члену [`tp_dealloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):404405```c406.tp_dealloc = (destructor) Custom_dealloc,407```408409Этот метод сначала сбрасывает счётчики ссылок двух атрибутов Python. [`Py_XDECREF()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_XDECREF) корректно обрабатывает случай, когда его аргумент равен `NULL` (что может произойти, если `tp_new` не выполнился до конца). Затем он вызывает член [`tp_free`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта (вычисленный с помощью `Py_TYPE(self)`) для освобождения памяти объекта. Обратите внимание, что тип объекта может не быть `CustomType`, поскольку объект может быть экземпляром подкласса.410411> **Примечание**412>413> Явное приведение к `destructor` выше необходимо, потому что мы определили `Custom_dealloc` как принимающий аргумент `CustomObject *`, но указатель на функцию `tp_dealloc` ожидает получения аргумента `PyObject *`. В противном случае компилятор выдаст предупреждение. Это объектно-ориентированный полиморфизм в C!414415Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому мы предоставляем реализацию `tp_new`:416417```c418static PyObject *419Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)420{421 CustomObject *self;422 self = (CustomObject *) type->tp_alloc(type, 0);423 if (self != NULL) {424 self->first = PyUnicode_FromString("");425 if (self->first == NULL) {426 Py_DECREF(self);427 return NULL;428 }429 self->last = PyUnicode_FromString("");430 if (self->last == NULL) {431 Py_DECREF(self);432 return NULL;433 }434 self->number = 0;435 }436 return (PyObject *) self;437}438```439440и устанавливаем его в член [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new):441442```c443.tp_new = Custom_new,444```445446Обработчик `tp_new` отвечает за создание (в отличие от инициализации) объектов типа. Он предоставляется в Python как метод [`__new__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__new__). Не требуется определять член `tp_new`, и действительно, многие расширения типов просто повторно используют [`PyType_GenericNew()`](https://python-all.ru/3.8/c-api/type.html#c.PyType_GenericNew), как это сделано в первой версии типа `Custom` выше. В этом случае мы используем обработчик `tp_new` для инициализации атрибутов `first` и `last` значениями по умолчанию, отличными от `NULL`.447448`tp_new` передаётся тип, экземпляр которого создаётся (не обязательно `CustomType`, если создаётся экземпляр подкласса), и любые аргументы, переданные при вызове типа; ожидается, что он вернёт созданный экземпляр. Обработчики `tp_new` всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам (также известным как `tp_init` в C или `__init__` в Python).449450> **Примечание**451>452> `tp_new` не должен вызывать `tp_init` явно, так как интерпретатор сделает это сам.453454Реализация `tp_new` вызывает слот [`tp_alloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти:455456```c457self = (CustomObject *) type->tp_alloc(type, 0);458```459460Поскольку выделение памяти может завершиться неудачей, мы должны проверить результат [`tp_alloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_alloc) на `NULL` перед продолжением.461462> **Примечание**463>464> Мы не заполняли слот [`tp_alloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.8/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.8/library/functions.html#object). Большинство типов используют стратегию выделения по умолчанию.465466> **Примечание**467>468> При создании кооперативного [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new) (такого, который вызывает [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new) или [`__new__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__new__) базового типа) нельзя *не* пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте тип, который будете вызывать, и вызывайте его [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуются от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.8/library/exceptions.html#TypeError).)469470Мы также определяем функцию инициализации, которая принимает аргументы для предоставления начальных значений экземпляру:471472```c473static int474Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)475{476 static char *kwlist[] = {"first", "last", "number", NULL};477 PyObject *first = NULL, *last = NULL, *tmp;478479 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,480 &first, &last,481 &self->number))482 return -1;483484 if (first) {485 tmp = self->first;486 Py_INCREF(first);487 self->first = first;488 Py_XDECREF(tmp);489 }490 if (last) {491 tmp = self->last;492 Py_INCREF(last);493 self->last = last;494 Py_XDECREF(tmp);495 }496 return 0;497}498```499500заполняя слот [`tp_init`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_init).501502```c503.tp_init = (initproc) Custom_init,504```505506Слот [`tp_init`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_init) открыт в Python как метод [`__init__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. Инициализаторы всегда принимают позиционные и именованные аргументы и должны возвращать либо `0` в случае успеха, либо `-1` в случае ошибки.507508В отличие от обработчика `tp_new`, нет никакой гарантии, что `tp_init` будет вызван (например, модуль [`pickle`](https://python-all.ru/3.8/library/pickle.html#module-pickle) по умолчанию не вызывает [`__init__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__init__) для распакованных экземпляров). Он также может быть вызван несколько раз. Кто угодно может вызвать метод [`__init__()`](https://python-all.ru/3.8/reference/datamodel.html#object.__init__) на наших объектах. По этой причине нужно быть особенно осторожным при присвоении новых значений атрибутов. Может возникнуть соблазн, например, присвоить члену `first` такое значение:509510```c511if (first) {512 Py_XDECREF(self->first);513 Py_INCREF(first);514 self->first = first;515}516```517518But 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.8/glossary.html#term-gil) and let arbitrary code run in other threads that accesses and modifies our object.519520Чтобы быть параноидальными и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?521522- когда мы точно знаем, что счётчик ссылок больше 1;523- when we know that deallocation of the object [1](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id5) will neither release the [GIL](https://python-all.ru/3.8/glossary.html#term-gil) nor cause any calls back into our type’s code;524- когда уменьшается счётчик ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) для типа, который не поддерживает циклическую сборку мусора [2](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id6).525526Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:527528```c529static PyMemberDef Custom_members[] = {530 {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,531 "first name"},532 {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,533 "last name"},534 {"number", T_INT, offsetof(CustomObject, number), 0,535 "custom number"},536 {NULL} /* Страж */537};538```539540и поместить определения в слот [`tp_members`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_members):541542```c543.tp_members = Custom_members,544```545546Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Общее управление атрибутами](https://python-all.ru/3.8/extending/newtypes.html#generic-attribute-management) ниже.547548Недостаток этого подхода в том, что он не даёт способа ограничить типы объектов, которые можно присваивать атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Кроме того, атрибуты можно удалить, установив указатели C в `NULL`. Даже если мы можем гарантировать, что члены инициализируются значениями, отличными от `NULL`, члены могут быть установлены в `NULL`, если атрибуты удалены.549550Мы определяем один метод `Custom.name()`, который выводит имя объекта как конкатенацию имени и фамилии.551552```c553static PyObject *554Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))555{556 if (self->first == NULL) {557 PyErr_SetString(PyExc_AttributeError, "first");558 return NULL;559 }560 if (self->last == NULL) {561 PyErr_SetString(PyExc_AttributeError, "last");562 return NULL;563 }564 return PyUnicode_FromFormat("%S %S", self->first, self->last);565}566```567568Метод реализован как функция на C, которая принимает экземпляр `Custom` (или подкласс `Custom`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:569570```python571def name(self):572 return "%s %s" % (self.first, self.last)573```574575Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны `NULL`. Это потому, что их можно удалить, и в этом случае они устанавливаются в `NULL`. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Как это сделать, мы увидим в следующем разделе.576577Теперь, когда мы определили метод, нужно создать массив определений методов:578579```c580static PyMethodDef Custom_methods[] = {581 {"name", (PyCFunction) Custom_name, METH_NOARGS,582 "Return the name, combining the first and last name"583 },584 {NULL} /* Страж */585};586```587588(обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.8/c-api/structures.html#METH_NOARGS), чтобы указать, что метод не ожидает аргументов, кроме *self*)589590и назначить его слоту [`tp_methods`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_methods):591592```c593.tp_methods = Custom_methods,594```595596Наконец, сделаем наш тип пригодным для использования в качестве базового класса для создания подклассов. Мы написали наши методы достаточно осторожно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.8/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флага нашего класса:597598```c599.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,600```601602Мы переименовываем `PyInit_custom()` в `PyInit_custom2()`, обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.8/c-api/module.html#c.PyModuleDef) и обновляем полное имя класса в структуре [`PyTypeObject`](https://python-all.ru/3.8/c-api/type.html#c.PyTypeObject).603604Наконец, мы обновляем наш файл `setup.py`, чтобы собрать новый модуль:605606```python607from distutils.core import setup, Extension608setup(name="custom", version="1.0",609 ext_modules=[610 Extension("custom", ["custom.c"]),611 Extension("custom2", ["custom2.c"]),612 ])613```614615## 2.3. Предоставление более точного управления атрибутами данных616617В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Custom`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.618619```c620#define PY_SSIZE_T_CLEAN621#include <Python.h>622#include "structmember.h"623624typedef struct {625 PyObject_HEAD626 PyObject *first; /* имя */627 PyObject *last; /* фамилия */628 int number;629} CustomObject;630631static void632Custom_dealloc(CustomObject *self)633{634 Py_XDECREF(self->first);635 Py_XDECREF(self->last);636 Py_TYPE(self)->tp_free((PyObject *) self);637}638639static PyObject *640Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)641{642 CustomObject *self;643 self = (CustomObject *) type->tp_alloc(type, 0);644 if (self != NULL) {645 self->first = PyUnicode_FromString("");646 if (self->first == NULL) {647 Py_DECREF(self);648 return NULL;649 }650 self->last = PyUnicode_FromString("");651 if (self->last == NULL) {652 Py_DECREF(self);653 return NULL;654 }655 self->number = 0;656 }657 return (PyObject *) self;658}659660static int661Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)662{663 static char *kwlist[] = {"first", "last", "number", NULL};664 PyObject *first = NULL, *last = NULL, *tmp;665666 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,667 &first, &last,668 &self->number))669 return -1;670671 if (first) {672 tmp = self->first;673 Py_INCREF(first);674 self->first = first;675 Py_DECREF(tmp);676 }677 if (last) {678 tmp = self->last;679 Py_INCREF(last);680 self->last = last;681 Py_DECREF(tmp);682 }683 return 0;684}685686static PyMemberDef Custom_members[] = {687 {"number", T_INT, offsetof(CustomObject, number), 0,688 "custom number"},689 {NULL} /* Страж */690};691692static PyObject *693Custom_getfirst(CustomObject *self, void *closure)694{695 Py_INCREF(self->first);696 return self->first;697}698699static int700Custom_setfirst(CustomObject *self, PyObject *value, void *closure)701{702 PyObject *tmp;703 if (value == NULL) {704 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");705 return -1;706 }707 if (!PyUnicode_Check(value)) {708 PyErr_SetString(PyExc_TypeError,709 "The first attribute value must be a string");710 return -1;711 }712 tmp = self->first;713 Py_INCREF(value);714 self->first = value;715 Py_DECREF(tmp);716 return 0;717}718719static PyObject *720Custom_getlast(CustomObject *self, void *closure)721{722 Py_INCREF(self->last);723 return self->last;724}725726static int727Custom_setlast(CustomObject *self, PyObject *value, void *closure)728{729 PyObject *tmp;730 if (value == NULL) {731 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");732 return -1;733 }734 if (!PyUnicode_Check(value)) {735 PyErr_SetString(PyExc_TypeError,736 "The last attribute value must be a string");737 return -1;738 }739 tmp = self->last;740 Py_INCREF(value);741 self->last = value;742 Py_DECREF(tmp);743 return 0;744}745746static PyGetSetDef Custom_getsetters[] = {747 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,748 "first name", NULL},749 {"last", (getter) Custom_getlast, (setter) Custom_setlast,750 "last name", NULL},751 {NULL} /* Страж */752};753754static PyObject *755Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))756{757 return PyUnicode_FromFormat("%S %S", self->first, self->last);758}759760static PyMethodDef Custom_methods[] = {761 {"name", (PyCFunction) Custom_name, METH_NOARGS,762 "Return the name, combining the first and last name"763 },764 {NULL} /* Страж */765};766767static PyTypeObject CustomType = {768 PyVarObject_HEAD_INIT(NULL, 0)769 .tp_name = "custom3.Custom",770 .tp_doc = "Custom objects",771 .tp_basicsize = sizeof(CustomObject),772 .tp_itemsize = 0,773 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,774 .tp_new = Custom_new,775 .tp_init = (initproc) Custom_init,776 .tp_dealloc = (destructor) Custom_dealloc,777 .tp_members = Custom_members,778 .tp_methods = Custom_methods,779 .tp_getset = Custom_getsetters,780};781782static PyModuleDef custommodule = {783 PyModuleDef_HEAD_INIT,784 .m_name = "custom3",785 .m_doc = "Example module that creates an extension type.",786 .m_size = -1,787};788789PyMODINIT_FUNC790PyInit_custom3(void)791{792 PyObject *m;793 if (PyType_Ready(&CustomType) < 0)794 return NULL;795796 m = PyModule_Create(&custommodule);797 if (m == NULL)798 return NULL;799800 Py_INCREF(&CustomType);801 if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {802 Py_DECREF(&CustomType);803 Py_DECREF(m);804 return NULL;805 }806807 return m;808}809```810811Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:812813```c814static PyObject *815Custom_getfirst(CustomObject *self, void *closure)816{817 Py_INCREF(self->first);818 return self->first;819}820821static int822Custom_setfirst(CustomObject *self, PyObject *value, void *closure)823{824 PyObject *tmp;825 if (value == NULL) {826 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");827 return -1;828 }829 if (!PyUnicode_Check(value)) {830 PyErr_SetString(PyExc_TypeError,831 "The first attribute value must be a string");832 return -1;833 }834 tmp = self->first;835 Py_INCREF(value);836 self->first = value;837 Py_DECREF(tmp);838 return 0;839}840```841842Функция получения принимает объект `Custom` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это может быть использовано, например, для создания единого набора функций получения и установки, которые решают, какой атрибут получить или установить на основе данных в замыкании.)843844Функция установки принимает объект `Custom`, новое значение и замыкание. Новое значение может быть `NULL`, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или его новое значение не является строкой.845846Мы создаём массив структур [`PyGetSetDef`](https://python-all.ru/3.8/c-api/structures.html#c.PyGetSetDef):847848```c849static PyGetSetDef Custom_getsetters[] = {850 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,851 "first name", NULL},852 {"last", (getter) Custom_getlast, (setter) Custom_setlast,853 "last name", NULL},854 {NULL} /* Страж */855};856```857858и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_getset):859860```c861.tp_getset = Custom_getsetters,862```863864Последний элемент структуры [`PyGetSetDef`](https://python-all.ru/3.8/c-api/structures.html#c.PyGetSetDef) – это «замыкание», упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём `NULL`.865866Также удаляем определения членов для этих атрибутов:867868```c869static PyMemberDef Custom_members[] = {870 {"number", T_INT, offsetof(CustomObject, number), 0,871 "custom number"},872 {NULL} /* Страж */873};874```875876Также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [3](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id7):877878```c879static int880Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)881{882 static char *kwlist[] = {"first", "last", "number", NULL};883 PyObject *first = NULL, *last = NULL, *tmp;884885 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,886 &first, &last,887 &self->number))888 return -1;889890 if (first) {891 tmp = self->first;892 Py_INCREF(first);893 self->first = first;894 Py_DECREF(tmp);895 }896 if (last) {897 tmp = self->last;898 Py_INCREF(last);899 self->last = last;900 Py_DECREF(tmp);901 }902 return 0;903}904```905906Благодаря этим изменениям можно гарантировать, что члены `first` и `last` никогда не будут `NULL`, поэтому проверки на значения `NULL` можно убрать почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_DECREF). Единственное место, где эти вызовы нельзя изменить, – реализация `tp_dealloc`, где возможна ситуация, что инициализация этих членов завершилась неудачей в `tp_new`.907908Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.909910## 2.4. Поддержка циклической сборки мусора911912Python имеет [циклический сборщик мусора (GC)](https://python-all.ru/3.8/glossary.html#term-garbage-collection), который может определять ненужные объекты даже при ненулевом счётчике ссылок. Это возможно, когда объекты образуют циклы. Например, рассмотрим:913914```pycon915>>> l = []916>>> l.append(l)917>>> del l918```919920В этом примере создаётся список, содержащий сам себя. При его удалении у него всё ещё остаётся ссылка на самого себя. Счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.921922Во второй версии примера `Custom` мы разрешили хранить объекты любого типа в атрибутах `first` или `last` [4](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id8). Кроме того, во второй и третьей версиях мы разрешили наследование от `Custom`, а подклассы могут добавлять произвольные атрибуты. По любой из этих двух причин объекты `Custom` могут участвовать в циклах:923924```pycon925>>> import custom3926>>> class Derived(custom3.Custom): pass927...928>>> n = Derived()929>>> n.some_attribute = n930```931932Чтобы экземпляр `Custom`, участвующий в цикле ссылок, был корректно обнаружен и собран циклическим GC, наш тип `Custom` должен заполнить два дополнительных слота и включить флаг, активирующий эти слоты:933934```c935#define PY_SSIZE_T_CLEAN936#include <Python.h>937#include "structmember.h"938939typedef struct {940 PyObject_HEAD941 PyObject *first; /* имя */942 PyObject *last; /* фамилия */943 int number;944} CustomObject;945946static int947Custom_traverse(CustomObject *self, visitproc visit, void *arg)948{949 Py_VISIT(self->first);950 Py_VISIT(self->last);951 return 0;952}953954static int955Custom_clear(CustomObject *self)956{957 Py_CLEAR(self->first);958 Py_CLEAR(self->last);959 return 0;960}961962static void963Custom_dealloc(CustomObject *self)964{965 PyObject_GC_UnTrack(self);966 Custom_clear(self);967 Py_TYPE(self)->tp_free((PyObject *) self);968}969970static PyObject *971Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)972{973 CustomObject *self;974 self = (CustomObject *) type->tp_alloc(type, 0);975 if (self != NULL) {976 self->first = PyUnicode_FromString("");977 if (self->first == NULL) {978 Py_DECREF(self);979 return NULL;980 }981 self->last = PyUnicode_FromString("");982 if (self->last == NULL) {983 Py_DECREF(self);984 return NULL;985 }986 self->number = 0;987 }988 return (PyObject *) self;989}990991static int992Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)993{994 static char *kwlist[] = {"first", "last", "number", NULL};995 PyObject *first = NULL, *last = NULL, *tmp;996997 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,998 &first, &last,999 &self->number))1000 return -1;10011002 if (first) {1003 tmp = self->first;1004 Py_INCREF(first);1005 self->first = first;1006 Py_DECREF(tmp);1007 }1008 if (last) {1009 tmp = self->last;1010 Py_INCREF(last);1011 self->last = last;1012 Py_DECREF(tmp);1013 }1014 return 0;1015}10161017static PyMemberDef Custom_members[] = {1018 {"number", T_INT, offsetof(CustomObject, number), 0,1019 "custom number"},1020 {NULL} /* Страж */1021};10221023static PyObject *1024Custom_getfirst(CustomObject *self, void *closure)1025{1026 Py_INCREF(self->first);1027 return self->first;1028}10291030static int1031Custom_setfirst(CustomObject *self, PyObject *value, void *closure)1032{1033 if (value == NULL) {1034 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");1035 return -1;1036 }1037 if (!PyUnicode_Check(value)) {1038 PyErr_SetString(PyExc_TypeError,1039 "The first attribute value must be a string");1040 return -1;1041 }1042 Py_INCREF(value);1043 Py_CLEAR(self->first);1044 self->first = value;1045 return 0;1046}10471048static PyObject *1049Custom_getlast(CustomObject *self, void *closure)1050{1051 Py_INCREF(self->last);1052 return self->last;1053}10541055static int1056Custom_setlast(CustomObject *self, PyObject *value, void *closure)1057{1058 if (value == NULL) {1059 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");1060 return -1;1061 }1062 if (!PyUnicode_Check(value)) {1063 PyErr_SetString(PyExc_TypeError,1064 "The last attribute value must be a string");1065 return -1;1066 }1067 Py_INCREF(value);1068 Py_CLEAR(self->last);1069 self->last = value;1070 return 0;1071}10721073static PyGetSetDef Custom_getsetters[] = {1074 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,1075 "first name", NULL},1076 {"last", (getter) Custom_getlast, (setter) Custom_setlast,1077 "last name", NULL},1078 {NULL} /* Страж */1079};10801081static PyObject *1082Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))1083{1084 return PyUnicode_FromFormat("%S %S", self->first, self->last);1085}10861087static PyMethodDef Custom_methods[] = {1088 {"name", (PyCFunction) Custom_name, METH_NOARGS,1089 "Return the name, combining the first and last name"1090 },1091 {NULL} /* Страж */1092};10931094static PyTypeObject CustomType = {1095 PyVarObject_HEAD_INIT(NULL, 0)1096 .tp_name = "custom4.Custom",1097 .tp_doc = "Custom objects",1098 .tp_basicsize = sizeof(CustomObject),1099 .tp_itemsize = 0,1100 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1101 .tp_new = Custom_new,1102 .tp_init = (initproc) Custom_init,1103 .tp_dealloc = (destructor) Custom_dealloc,1104 .tp_traverse = (traverseproc) Custom_traverse,1105 .tp_clear = (inquiry) Custom_clear,1106 .tp_members = Custom_members,1107 .tp_methods = Custom_methods,1108 .tp_getset = Custom_getsetters,1109};11101111static PyModuleDef custommodule = {1112 PyModuleDef_HEAD_INIT,1113 .m_name = "custom4",1114 .m_doc = "Example module that creates an extension type.",1115 .m_size = -1,1116};11171118PyMODINIT_FUNC1119PyInit_custom4(void)1120{1121 PyObject *m;1122 if (PyType_Ready(&CustomType) < 0)1123 return NULL;11241125 m = PyModule_Create(&custommodule);1126 if (m == NULL)1127 return NULL;11281129 Py_INCREF(&CustomType);1130 if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {1131 Py_DECREF(&CustomType);1132 Py_DECREF(m);1133 return NULL;1134 }11351136 return m;1137}1138```11391140Во-первых, метод обхода (traversal) сообщает циклическому GC о подобъектах, которые могут участвовать в циклах:11411142```c1143static int1144Custom_traverse(CustomObject *self, visitproc visit, void *arg)1145{1146 int vret;1147 if (self->first) {1148 vret = visit(self->first, arg);1149 if (vret != 0)1150 return vret;1151 }1152 if (self->last) {1153 vret = visit(self->last, arg);1154 if (vret != 0)1155 return vret;1156 }1157 return 0;1158}1159```11601161Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.11621163Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.8/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.8/c-api/gcsupport.html#c.Py_VISIT) можно минимизировать количество шаблонного кода в `Custom_traverse`:11641165```c1166static int1167Custom_traverse(CustomObject *self, visitproc visit, void *arg)1168{1169 Py_VISIT(self->first);1170 Py_VISIT(self->last);1171 return 0;1172}1173```11741175> **Примечание**1176>1177> Реализация [`tp_traverse`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна именовать свои аргументы именно как *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.8/c-api/gcsupport.html#c.Py_VISIT).11781179Во-вторых, нужно предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах:11801181```c1182static int1183Custom_clear(CustomObject *self)1184{1185 Py_CLEAR(self->first);1186 Py_CLEAR(self->last);1187 return 0;1188}1189```11901191Обратите внимание на использование макроса [`Py_CLEAR()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_CLEAR). Это рекомендуемый и безопасный способ очистки атрибутов данных произвольных типов с одновременным уменьшением их счётчиков ссылок. Если вместо этого вызвать [`Py_XDECREF()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_XDECREF) для атрибута перед установкой его в `NULL`, существует вероятность, что деструктор атрибута повторно вызовет код, читающий этот атрибут (*особенно* при наличии циклической ссылки).11921193> **Примечание**1194>1195> Можно эмулировать [`Py_CLEAR()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_CLEAR) следующим образом:1196>1197> ```c1198> PyObject *tmp;1199> tmp = self->first;1200> self->first = NULL;1201> Py_XDECREF(tmp);1202> ```1203>1204> Тем не менее, гораздо проще и менее чревато ошибками всегда использовать [`Py_CLEAR()`](https://python-all.ru/3.8/c-api/refcounting.html#c.Py_CLEAR) при удалении атрибута. Не пытайтесь микрооптимизировать в ущерб надёжности!12051206Деаллокатор `Custom_dealloc` может вызывать произвольный код при очистке атрибутов. Это означает, что циклический GC может быть запущен внутри функции. Поскольку GC предполагает, что счётчик ссылок не равен нулю, нужно отменить отслеживание объекта GC с помощью вызова [`PyObject_GC_UnTrack()`](https://python-all.ru/3.8/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой членов. Вот наша переработанная версия деаллокатора с использованием [`PyObject_GC_UnTrack()`](https://python-all.ru/3.8/c-api/gcsupport.html#c.PyObject_GC_UnTrack) и `Custom_clear`:12071208```c1209static void1210Custom_dealloc(CustomObject *self)1211{1212 PyObject_GC_UnTrack(self);1213 Custom_clear(self);1214 Py_TYPE(self)->tp_free((PyObject *) self);1215}1216```12171218Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.8/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:12191220```c1221.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1222```12231224Вот, в общем-то, и всё. Если бы мы написали собственные обработчики [`tp_alloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_free), их пришлось бы доработать для поддержки циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.12251226## 2.5. Наследование от других типов12271228Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему [`PyTypeObject`](https://python-all.ru/3.8/c-api/type.html#c.PyTypeObject). Совместное использование структур [`PyTypeObject`](https://python-all.ru/3.8/c-api/type.html#c.PyTypeObject) между модулями расширений может быть затруднительным.12291230В этом примере мы создадим тип `SubList`, наследующий от встроенного типа [`list`](https://python-all.ru/3.8/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик:12311232```pycon1233>>> import sublist1234>>> s = sublist.SubList(range(3))1235>>> s.extend(s)1236>>> print(len(s))123761238>>> print(s.increment())123911240>>> print(s.increment())124121242```12431244```c1245#define PY_SSIZE_T_CLEAN1246#include <Python.h>12471248typedef struct {1249 PyListObject list;1250 int state;1251} SubListObject;12521253static PyObject *1254SubList_increment(SubListObject *self, PyObject *unused)1255{1256 self->state++;1257 return PyLong_FromLong(self->state);1258}12591260static PyMethodDef SubList_methods[] = {1261 {"increment", (PyCFunction) SubList_increment, METH_NOARGS,1262 PyDoc_STR("increment state counter")},1263 {NULL},1264};12651266static int1267SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1268{1269 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1270 return -1;1271 self->state = 0;1272 return 0;1273}12741275static PyTypeObject SubListType = {1276 PyVarObject_HEAD_INIT(NULL, 0)1277 .tp_name = "sublist.SubList",1278 .tp_doc = "SubList objects",1279 .tp_basicsize = sizeof(SubListObject),1280 .tp_itemsize = 0,1281 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,1282 .tp_init = (initproc) SubList_init,1283 .tp_methods = SubList_methods,1284};12851286static PyModuleDef sublistmodule = {1287 PyModuleDef_HEAD_INIT,1288 .m_name = "sublist",1289 .m_doc = "Example module that creates an extension type.",1290 .m_size = -1,1291};12921293PyMODINIT_FUNC1294PyInit_sublist(void)1295{1296 PyObject *m;1297 SubListType.tp_base = &PyList_Type;1298 if (PyType_Ready(&SubListType) < 0)1299 return NULL;13001301 m = PyModule_Create(&sublistmodule);1302 if (m == NULL)1303 return NULL;13041305 Py_INCREF(&SubListType);1306 if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {1307 Py_DECREF(&SubListType);1308 Py_DECREF(m);1309 return NULL;1310 }13111312 return m;1313}1314```13151316Как видите, исходный код очень похож на примеры `Custom` из предыдущих разделов. Разберём основные различия между ними.13171318```c1319typedef struct {1320 PyListObject list;1321 int state;1322} SubListObject;1323```13241325Основное отличие для объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.8/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.13261327Когда объект Python является экземпляром `SubList`, его указатель `PyObject *` можно безопасно приводить как к `PyListObject *`, так и к `SubListObject *`:13281329```c1330static int1331SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1332{1333 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1334 return -1;1335 self->state = 0;1336 return 0;1337}1338```13391340Выше показано, как вызывать метод [`__init__`](https://python-all.ru/3.8/reference/datamodel.html#object.__init__) базового типа.13411342Этот шаблон важен при написании типа с пользовательскими членами [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_dealloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Обработчик [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new) не должен фактически выделять память для объекта с помощью своего [`tp_alloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_alloc), а должен позволить базовому классу обработать это, вызвав его собственный [`tp_new`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_new).13431344Структура [`PyTypeObject`](https://python-all.ru/3.8/c-api/type.html#c.PyTypeObject) поддерживает [`tp_base`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_base) указывающее конкретный базовый класс типа. Из-за проблем с кроссплатформенными компиляторами нельзя заполнить это поле напрямую ссылкой на [`PyList_Type`](https://python-all.ru/3.8/c-api/list.html#c.PyList_Type); это следует сделать позже, в функции инициализации модуля:13451346```c1347PyMODINIT_FUNC1348PyInit_sublist(void)1349{1350 PyObject* m;1351 SubListType.tp_base = &PyList_Type;1352 if (PyType_Ready(&SubListType) < 0)1353 return NULL;13541355 m = PyModule_Create(&sublistmodule);1356 if (m == NULL)1357 return NULL;13581359 Py_INCREF(&SubListType);1360 if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {1361 Py_DECREF(&SubListType);1362 Py_DECREF(m);1363 return NULL;1364 }13651366 return m;1367}1368```13691370Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.8/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании от существующего типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.8/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.13711372После этого вызов [`PyType_Ready()`](https://python-all.ru/3.8/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Custom`.13731374Сноски13751376**[1](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id1)**13771378Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой.13791380**[2](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id2)**13811382На это полагались в обработчике [`tp_dealloc`](https://python-all.ru/3.8/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) в данном примере, поскольку тип не поддерживает сборку мусора.13831384**[3](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id3)**13851386Теперь известно, что первый и последний элементы являются строками, поэтому можно было бы менее осторожно уменьшать их счётчики ссылок. Однако допускаются экземпляры подклассов строк. Даже если освобождение обычных строк не вызовет обратного вызова в наши объекты, нельзя гарантировать, что освобождение экземпляра подкласса строки не вызовет такого обратного вызова.13871388**[4](https://python-all.ru/3.8/extending/newtypes_tutorial.html#id4)**13891390Кроме того, даже если атрибуты ограничены экземплярами строк, пользователь может передать произвольные подклассы [`str`](https://python-all.ru/3.8/library/stdtypes.html#str) и тем самым всё равно создать циклические ссылки.1391