newtypes_tutorial.md
1> **Источник:** https://python-all.ru/3.6/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.6/library/stdtypes.html#str) и [`list`](https://python-all.ru/3.6/library/stdtypes.html#list). Код для всех типов расширений следует определённому шаблону, но есть некоторые детали, которые необходимо понять, прежде чем приступить к работе. Этот документ является вводным руководством по данной теме.1011## 2.1. Основы1213Среда выполнения [CPython](https://python-all.ru/3.6/glossary.html#term-cpython) рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/3.6/c-api/structures.html#c.PyObject), который служит «базовым типом» для всех объектов Python. Сама структура [`PyObject`](https://python-all.ru/3.6/c-api/structures.html#c.PyObject) содержит только [счётчик ссылок](https://python-all.ru/3.6/glossary.html#term-reference-count) объекта и указатель на «объект типа» объекта. В этом и заключается суть: объект типа определяет, какие (C) функции вызываются интерпретатором, когда, например, выполняется поиск атрибута объекта, вызов метода или его умножение на другой объект. Эти C-функции называются «методами типа».1415Итак, чтобы определить новый тип расширения, необходимо создать новый объект типа.1617Такие вещи можно объяснить только на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип с именем `Custom` внутри C-модуля расширения `custom`:1819> **Примечание**20>21> То, что мы показываем здесь, – это традиционный способ определения *статических* типов расширений. Он должен быть достаточным для большинства случаев. C API также позволяет определять типы расширений, размещаемые в куче, с помощью функции [`PyType_FromSpec()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_FromSpec), которая не рассматривается в этом руководстве.2223```c24#include <Python.h>2526typedef struct {27 PyObject_HEAD28 /* Сюда помещаются поля, специфичные для типа. */29} CustomObject;3031static PyTypeObject CustomType = {32 PyVarObject_HEAD_INIT(NULL, 0)33 .tp_name = "custom.Custom",34 .tp_doc = "Custom objects",35 .tp_basicsize = sizeof(CustomObject),36 .tp_itemsize = 0,37 .tp_flags = Py_TPFLAGS_DEFAULT,38 .tp_new = PyType_GenericNew,39};4041static PyModuleDef custommodule = {42 PyModuleDef_HEAD_INIT,43 .m_name = "custom",44 .m_doc = "Example module that creates an extension type.",45 .m_size = -1,46};4748PyMODINIT_FUNC49PyInit_custom(void)50{51 PyObject *m;52 if (PyType_Ready(&CustomType) < 0)53 return NULL;5455 m = PyModule_Create(&custommodule);56 if (m == NULL)57 return NULL;5859 Py_INCREF(&CustomType);60 PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);61 return m;62}63```6465Это довольно много для одномоментного восприятия, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы. Этот файл определяет три вещи:66671. Что содержит `Custom` **объект**: это структура `CustomObject`, которая выделяется один раз для каждого экземпляра `Custom`.682. Как ведёт себя `Custom` **тип**: это структура `CustomType`, которая определяет набор флагов и указателей на функции, которые интерпретатор проверяет при запросе определённых операций.693. Как инициализировать модуль `custom`: это `PyInit_custom` функция и связанная с ней структура `custommodule`.7071Первая часть:7273```c74typedef struct {75 PyObject_HEAD76} CustomObject;77```7879Вот что будет содержать пользовательский объект. `PyObject_HEAD` является обязательным в начале каждой структуры объекта и определяет поле `ob_base` типа [`PyObject`](https://python-all.ru/3.6/c-api/structures.html#c.PyObject), содержащее указатель на объект типа и счётчик ссылок (доступ к ним можно получить с помощью макросов [`Py_REFCNT`](https://python-all.ru/3.6/c-api/structures.html#c.Py_REFCNT) и [`Py_TYPE`](https://python-all.ru/3.6/c-api/structures.html#c.Py_TYPE) соответственно). Причина использования макроса – абстрагирование от компоновки и возможность добавления дополнительных полей в отладочных сборках.8081> **Примечание**82>83> В приведённом выше коде нет точки с запятой после макроса [`PyObject_HEAD`](https://python-all.ru/3.6/c-api/structures.html#c.PyObject_HEAD). Будьте осторожны, чтобы случайно её не добавить: некоторые компиляторы выдадут ошибку.8485Конечно, объекты обычно хранят дополнительные данные помимо стандартного шаблона `PyObject_HEAD`; например, вот определение для стандартных чисел с плавающей запятой Python:8687```c88typedef struct {89 PyObject_HEAD90 double ob_fval;91} PyFloatObject;92```9394Вторая часть – это определение объекта типа.9596```c97static PyTypeObject CustomType = {98 PyVarObject_HEAD_INIT(NULL, 0)99 .tp_name = "custom.Custom",100 .tp_doc = "Custom objects",101 .tp_basicsize = sizeof(CustomObject),102 .tp_itemsize = 0,103 .tp_new = PyType_GenericNew,104};105```106107> **Примечание**108>109> Рекомендуется использовать именованные инициализаторы в стиле C99, как указано выше, чтобы не перечислять все поля [`PyTypeObject`](https://python-all.ru/3.6/c-api/type.html#c.PyTypeObject), которые не важны, а также не беспокоиться о порядке объявления полей.110111Фактическое определение [`PyTypeObject`](https://python-all.ru/3.6/c-api/type.html#c.PyTypeObject) в `object.h` содержит гораздо больше [полей](https://python-all.ru/3.6/c-api/typeobj.html#type-structs), чем приведённое выше определение. Оставшиеся поля будут заполнены нулями компилятором C, и обычно принято не указывать их явно, если они не нужны.112113Мы разберём его, поле за полем:114115```c116PyVarObject_HEAD_INIT(NULL, 0)117```118119Эта строка является обязательным шаблоном для инициализации поля `ob_base`, упомянутого выше.120121```c122.tp_name = "custom.Custom",123```124125Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:126127```pycon128>>> "" + custom.Custom()129Traceback (most recent call last):130 File "<stdin>", line 1, in <module>131TypeError: can only concatenate str (not "custom.Custom") to str132```133134Обратите внимание, что имя является составным (с точками) и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `custom`, а тип – `Custom`, поэтому мы устанавливаем имя типа `custom.Custom`. Использование реального составного пути импорта важно для совместимости вашего типа с модулями [`pydoc`](https://python-all.ru/3.6/library/pydoc.html#module-pydoc) и [`pickle`](https://python-all.ru/3.6/library/pickle.html#module-pickle).135136```c137.tp_basicsize = sizeof(CustomObject),138.tp_itemsize = 0,139```140141Это нужно, чтобы Python знал, сколько памяти выделять при создании новых экземпляров `Custom`. [`tp_itemsize`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_itemsize) используется только для объектов переменного размера, в остальных случаях должно быть равно нулю.142143> **Примечание**144>145> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.6/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для [`tp_basicsize`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.6/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.146147Мы устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.6/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).148149```c150.tp_flags = Py_TPFLAGS_DEFAULT,151```152153Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.154155Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_doc).156157```c158.tp_doc = "Custom objects",159```160161Чтобы разрешить создание объектов, мы должны предоставить обработчик [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new). Это эквивалент метода Python [`__new__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__new__), но должен быть указан явно. В данном случае мы можем просто использовать реализацию по умолчанию, предоставляемую API-функцией [`PyType_GenericNew()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_GenericNew).162163```c164.tp_new = PyType_GenericNew,165```166167Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_custom()`:168169```c170if (PyType_Ready(&CustomType) < 0)171 return;172```173174Это инициализирует тип `Custom`, заполняя ряд членов соответствующими значениями по умолчанию, включая `ob_type`, который мы изначально установили в *NULL*.175176```c177PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);178```179180Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Custom` вызовом класса `Custom`:181182```pycon183>>> import custom184>>> mycustom = custom.Custom()185```186187Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `custom.c` и:188189```python190from distutils.core import setup, Extension191setup(name="custom", version="1.0",192 ext_modules=[Extension("custom", ["custom.c"])])193```194195в файл с именем `setup.py`; затем введите196197```console198$ python setup.py build199```200201в командной оболочке должен создать файл `custom.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import custom` и поработать с объектами Custom.202203Было не так сложно, правда?204205Конечно, текущий тип Custom довольно неинтересен. У него нет данных, и он ничего не делает. Его даже нельзя унаследовать.206207> **Примечание**208>209> Хотя в этой документации демонстрируется стандартный модуль [`distutils`](https://python-all.ru/3.6/library/distutils.html#module-distutils) для создания расширений на C, в реальных проектах рекомендуется использовать более новую и лучше поддерживаемую библиотеку `setuptools`. Документация о том, как это сделать, выходит за рамки данного документа и находится в [Руководстве пользователя по упаковке Python](https://python-all.ru/3.6/extending/newtypes_tutorial.html).210211## 2.2. Добавление данных и методов к базовому примеру212213Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `custom2`, который добавляет эти возможности:214215```c216#include <Python.h>217#include "structmember.h"218219typedef struct {220 PyObject_HEAD221 PyObject *first; /* имя */222 PyObject *last; /* фамилия */223 int number;224} CustomObject;225226static void227Custom_dealloc(CustomObject *self)228{229 Py_XDECREF(self->first);230 Py_XDECREF(self->last);231 Py_TYPE(self)->tp_free((PyObject *) self);232}233234static PyObject *235Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)236{237 CustomObject *self;238 self = (CustomObject *) type->tp_alloc(type, 0);239 if (self != NULL) {240 self->first = PyUnicode_FromString("");241 if (self->first == NULL) {242 Py_DECREF(self);243 return NULL;244 }245 self->last = PyUnicode_FromString("");246 if (self->last == NULL) {247 Py_DECREF(self);248 return NULL;249 }250 self->number = 0;251 }252 return (PyObject *) self;253}254255static int256Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)257{258 static char *kwlist[] = {"first", "last", "number", NULL};259 PyObject *first = NULL, *last = NULL, *tmp;260261 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,262 &first, &last,263 &self->number))264 return -1;265266 if (first) {267 tmp = self->first;268 Py_INCREF(first);269 self->first = first;270 Py_XDECREF(tmp);271 }272 if (last) {273 tmp = self->last;274 Py_INCREF(last);275 self->last = last;276 Py_XDECREF(tmp);277 }278 return 0;279}280281static PyMemberDef Custom_members[] = {282 {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,283 "first name"},284 {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,285 "last name"},286 {"number", T_INT, offsetof(CustomObject, number), 0,287 "custom number"},288 {NULL} /* Страж */289};290291static PyObject *292Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))293{294 if (self->first == NULL) {295 PyErr_SetString(PyExc_AttributeError, "first");296 return NULL;297 }298 if (self->last == NULL) {299 PyErr_SetString(PyExc_AttributeError, "last");300 return NULL;301 }302 return PyUnicode_FromFormat("%S %S", self->first, self->last);303}304305static PyMethodDef Custom_methods[] = {306 {"name", (PyCFunction) Custom_name, METH_NOARGS,307 "Return the name, combining the first and last name"308 },309 {NULL} /* Страж */310};311312static PyTypeObject CustomType = {313 PyVarObject_HEAD_INIT(NULL, 0)314 .tp_name = "custom2.Custom",315 .tp_doc = "Custom objects",316 .tp_basicsize = sizeof(CustomObject),317 .tp_itemsize = 0,318 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,319 .tp_new = Custom_new,320 .tp_init = (initproc) Custom_init,321 .tp_dealloc = (destructor) Custom_dealloc,322 .tp_members = Custom_members,323 .tp_methods = Custom_methods,324};325326static PyModuleDef custommodule = {327 PyModuleDef_HEAD_INIT,328 .m_name = "custom2",329 .m_doc = "Example module that creates an extension type.",330 .m_size = -1,331};332333PyMODINIT_FUNC334PyInit_custom2(void)335{336 PyObject *m;337 if (PyType_Ready(&CustomType) < 0)338 return NULL;339340 m = PyModule_Create(&custommodule);341 if (m == NULL)342 return NULL;343344 Py_INCREF(&CustomType);345 PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);346 return m;347}348```349350Эта версия модуля содержит ряд изменений.351352Мы добавили дополнительный include:353354```c355#include <structmember.h>356```357358Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.359360Тип `Custom` теперь имеет три атрибута данных в своей C-структуре: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – это целое число C.361362Структура объекта обновляется соответствующим образом:363364```c365typedef struct {366 PyObject_HEAD367 PyObject *first; /* имя */368 PyObject *last; /* фамилия */369 int number;370} CustomObject;371```372373Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:374375```c376static void377Custom_dealloc(CustomObject *self)378{379 Py_XDECREF(self->first);380 Py_XDECREF(self->last);381 Py_TYPE(self)->tp_free((PyObject *) self);382}383```384385который присваивается члену [`tp_dealloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):386387```c388.tp_dealloc = (destructor) Custom_dealloc,389```390391Этот метод сначала очищает счетчики ссылок двух атрибутов Python. [`Py_XDECREF()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_XDECREF) корректно обрабатывает случай, когда его аргумент равен *NULL* (что может произойти, если `tp_new` не выполнился до конца). Затем он вызывает член [`tp_free`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта (вычисляемый с помощью `Py_TYPE(self)`) для освобождения памяти объекта. Обратите внимание, что тип объекта может не быть `CustomType`, поскольку объект может быть экземпляром подкласса.392393> **Примечание**394>395> Явное приведение к `destructor` выше необходимо, потому что мы определили `Custom_dealloc` как принимающий аргумент `CustomObject *`, но указатель на функцию `tp_dealloc` ожидает получения аргумента `PyObject *`. В противном случае компилятор выдаст предупреждение. Это объектно-ориентированный полиморфизм в C!396397Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому мы предоставляем реализацию `tp_new`:398399```c400static PyObject *401Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)402{403 CustomObject *self;404 self = (CustomObject *) type->tp_alloc(type, 0);405 if (self != NULL) {406 self->first = PyUnicode_FromString("");407 if (self->first == NULL) {408 Py_DECREF(self);409 return NULL;410 }411 self->last = PyUnicode_FromString("");412 if (self->last == NULL) {413 Py_DECREF(self);414 return NULL;415 }416 self->number = 0;417 }418 return (PyObject *) self;419}420```421422и устанавливаем его в член [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new):423424```c425.tp_new = Custom_new,426```427428Обработчик `tp_new` отвечает за создание (в отличие от инициализации) объектов данного типа. Он доступен в Python как метод [`__new__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__new__). Определять член `tp_new` не обязательно, и многие типы расширений просто переиспользуют [`PyType_GenericNew()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_GenericNew), как было сделано в первой версии типа `Custom` выше. В данном случае мы используем обработчик `tp_new` для инициализации атрибутов `first` и `last` значениями по умолчанию, отличными от *NULL*.429430`tp_new` передаётся тип, экземпляр которого создаётся (не обязательно `CustomType`, если создаётся экземпляр подкласса), и любые аргументы, переданные при вызове типа; ожидается, что он вернёт созданный экземпляр. Обработчики `tp_new` всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам (также известным как `tp_init` в C или `__init__` в Python).431432> **Примечание**433>434> `tp_new` не должен вызывать `tp_init` явно, так как интерпретатор сделает это сам.435436Реализация `tp_new` вызывает слот [`tp_alloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти:437438```c439self = (CustomObject *) type->tp_alloc(type, 0);440```441442Поскольку выделение памяти может завершиться неудачей, необходимо проверить результат [`tp_alloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_alloc) на *NULL* перед продолжением.443444> **Примечание**445>446> Мы не заполняли слот [`tp_alloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.6/library/functions.html#object). Большинство типов используют стратегию выделения по умолчанию.447448> **Примечание**449>450> При создании кооперативного [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new) (такого, который вызывает [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new) или [`__new__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__new__) базового типа) нельзя *не* пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте тип, который будете вызывать, и вызывайте его [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуются от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.6/library/exceptions.html#TypeError).)451452Мы также определяем функцию инициализации, которая принимает аргументы для предоставления начальных значений экземпляру:453454```c455static int456Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)457{458 static char *kwlist[] = {"first", "last", "number", NULL};459 PyObject *first = NULL, *last = NULL, *tmp;460461 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,462 &first, &last,463 &self->number))464 return -1;465466 if (first) {467 tmp = self->first;468 Py_INCREF(first);469 self->first = first;470 Py_XDECREF(tmp);471 }472 if (last) {473 tmp = self->last;474 Py_INCREF(last);475 self->last = last;476 Py_XDECREF(tmp);477 }478 return 0;479}480```481482заполняя слот [`tp_init`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_init).483484```c485.tp_init = (initproc) Custom_init,486```487488Слот [`tp_init`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_init) открыт в Python как метод [`__init__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. Инициализаторы всегда принимают позиционные и именованные аргументы и должны возвращать либо `0` в случае успеха, либо `-1` в случае ошибки.489490В отличие от обработчика `tp_new`, нет никакой гарантии, что `tp_init` будет вызван (например, модуль [`pickle`](https://python-all.ru/3.6/library/pickle.html#module-pickle) по умолчанию не вызывает [`__init__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__init__) для распакованных экземпляров). Он также может быть вызван несколько раз. Кто угодно может вызвать метод [`__init__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__init__) на наших объектах. По этой причине нужно быть особенно осторожным при присвоении новых значений атрибутов. Может возникнуть соблазн, например, присвоить члену `first` такое значение:491492```c493if (first) {494 Py_XDECREF(self->first);495 Py_INCREF(first);496 self->first = first;497}498```499500But 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.6/glossary.html#term-global-interpreter-lock) and let arbitrary code run in other threads that accesses and modifies our object.501502Чтобы быть параноидальными и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?503504- когда мы точно знаем, что счётчик ссылок больше 1;505- when we know that deallocation of the object [1](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id5) will neither release the [GIL](https://python-all.ru/3.6/glossary.html#term-gil) nor cause any calls back into our type’s code;506- когда уменьшается счётчик ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) для типа, который не поддерживает циклическую сборку мусора [2](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id6).507508Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:509510```c511static PyMemberDef Custom_members[] = {512 {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,513 "first name"},514 {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,515 "last name"},516 {"number", T_INT, offsetof(CustomObject, number), 0,517 "custom number"},518 {NULL} /* Страж */519};520```521522и поместить определения в слот [`tp_members`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_members):523524```c525.tp_members = Custom_members,526```527528Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Общее управление атрибутами](https://python-all.ru/3.6/extending/newtypes.html#generic-attribute-management) ниже.529530Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.531532Мы определяем один метод `Custom.name()`, который выводит имя объекта как конкатенацию имени и фамилии.533534```c535static PyObject *536Custom_name(CustomObject *self)537{538 if (self->first == NULL) {539 PyErr_SetString(PyExc_AttributeError, "first");540 return NULL;541 }542 if (self->last == NULL) {543 PyErr_SetString(PyExc_AttributeError, "last");544 return NULL;545 }546 return PyUnicode_FromFormat("%S %S", self->first, self->last);547}548```549550Метод реализован как функция на C, которая принимает экземпляр `Custom` (или подкласс `Custom`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:551552```python553def name(self):554 return "%s %s" % (self.first, self.last)555```556557Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать в следующем разделе.558559Теперь, когда мы определили метод, нужно создать массив определений методов:560561```c562static PyMethodDef Custom_methods[] = {563 {"name", (PyCFunction) Custom_name, METH_NOARGS,564 "Return the name, combining the first and last name"565 },566 {NULL} /* Страж */567};568```569570(обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.6/c-api/structures.html#METH_NOARGS), чтобы указать, что метод не ожидает аргументов, кроме *self*)571572и назначить его слоту [`tp_methods`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_methods):573574```c575.tp_methods = Custom_methods,576```577578Наконец, сделаем наш тип пригодным для использования в качестве базового класса для создания подклассов. Мы написали наши методы достаточно осторожно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.6/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флага нашего класса:579580```c581.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,582```583584Мы переименовываем `PyInit_custom()` в `PyInit_custom2()`, обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.6/c-api/module.html#c.PyModuleDef) и обновляем полное имя класса в структуре [`PyTypeObject`](https://python-all.ru/3.6/c-api/type.html#c.PyTypeObject).585586Наконец, мы обновляем наш файл `setup.py`, чтобы собрать новый модуль:587588```python589from distutils.core import setup, Extension590setup(name="custom", version="1.0",591 ext_modules=[592 Extension("custom", ["custom.c"]),593 Extension("custom2", ["custom2.c"]),594 ])595```596597## 2.3. Предоставление более точного управления атрибутами данных598599В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Custom`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.600601```c602#include <Python.h>603#include "structmember.h"604605typedef struct {606 PyObject_HEAD607 PyObject *first; /* имя */608 PyObject *last; /* фамилия */609 int number;610} CustomObject;611612static void613Custom_dealloc(CustomObject *self)614{615 Py_XDECREF(self->first);616 Py_XDECREF(self->last);617 Py_TYPE(self)->tp_free((PyObject *) self);618}619620static PyObject *621Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)622{623 CustomObject *self;624 self = (CustomObject *) type->tp_alloc(type, 0);625 if (self != NULL) {626 self->first = PyUnicode_FromString("");627 if (self->first == NULL) {628 Py_DECREF(self);629 return NULL;630 }631 self->last = PyUnicode_FromString("");632 if (self->last == NULL) {633 Py_DECREF(self);634 return NULL;635 }636 self->number = 0;637 }638 return (PyObject *) self;639}640641static int642Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)643{644 static char *kwlist[] = {"first", "last", "number", NULL};645 PyObject *first = NULL, *last = NULL, *tmp;646647 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,648 &first, &last,649 &self->number))650 return -1;651652 if (first) {653 tmp = self->first;654 Py_INCREF(first);655 self->first = first;656 Py_DECREF(tmp);657 }658 if (last) {659 tmp = self->last;660 Py_INCREF(last);661 self->last = last;662 Py_DECREF(tmp);663 }664 return 0;665}666667static PyMemberDef Custom_members[] = {668 {"number", T_INT, offsetof(CustomObject, number), 0,669 "custom number"},670 {NULL} /* Страж */671};672673static PyObject *674Custom_getfirst(CustomObject *self, void *closure)675{676 Py_INCREF(self->first);677 return self->first;678}679680static int681Custom_setfirst(CustomObject *self, PyObject *value, void *closure)682{683 PyObject *tmp;684 if (value == NULL) {685 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");686 return -1;687 }688 if (!PyUnicode_Check(value)) {689 PyErr_SetString(PyExc_TypeError,690 "The first attribute value must be a string");691 return -1;692 }693 tmp = self->first;694 Py_INCREF(value);695 self->first = value;696 Py_DECREF(tmp);697 return 0;698}699700static PyObject *701Custom_getlast(CustomObject *self, void *closure)702{703 Py_INCREF(self->last);704 return self->last;705}706707static int708Custom_setlast(CustomObject *self, PyObject *value, void *closure)709{710 PyObject *tmp;711 if (value == NULL) {712 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");713 return -1;714 }715 if (!PyUnicode_Check(value)) {716 PyErr_SetString(PyExc_TypeError,717 "The last attribute value must be a string");718 return -1;719 }720 tmp = self->last;721 Py_INCREF(value);722 self->last = value;723 Py_DECREF(tmp);724 return 0;725}726727static PyGetSetDef Custom_getsetters[] = {728 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,729 "first name", NULL},730 {"last", (getter) Custom_getlast, (setter) Custom_setlast,731 "last name", NULL},732 {NULL} /* Страж */733};734735static PyObject *736Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))737{738 return PyUnicode_FromFormat("%S %S", self->first, self->last);739}740741static PyMethodDef Custom_methods[] = {742 {"name", (PyCFunction) Custom_name, METH_NOARGS,743 "Return the name, combining the first and last name"744 },745 {NULL} /* Страж */746};747748static PyTypeObject CustomType = {749 PyVarObject_HEAD_INIT(NULL, 0)750 .tp_name = "custom3.Custom",751 .tp_doc = "Custom objects",752 .tp_basicsize = sizeof(CustomObject),753 .tp_itemsize = 0,754 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,755 .tp_new = Custom_new,756 .tp_init = (initproc) Custom_init,757 .tp_dealloc = (destructor) Custom_dealloc,758 .tp_members = Custom_members,759 .tp_methods = Custom_methods,760 .tp_getset = Custom_getsetters,761};762763static PyModuleDef custommodule = {764 PyModuleDef_HEAD_INIT,765 .m_name = "custom3",766 .m_doc = "Example module that creates an extension type.",767 .m_size = -1,768};769770PyMODINIT_FUNC771PyInit_custom3(void)772{773 PyObject *m;774 if (PyType_Ready(&CustomType) < 0)775 return NULL;776777 m = PyModule_Create(&custommodule);778 if (m == NULL)779 return NULL;780781 Py_INCREF(&CustomType);782 PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);783 return m;784}785```786787Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:788789```c790static PyObject *791Custom_getfirst(CustomObject *self, void *closure)792{793 Py_INCREF(self->first);794 return self->first;795}796797static int798Custom_setfirst(CustomObject *self, PyObject *value, void *closure)799{800 PyObject *tmp;801 if (value == NULL) {802 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");803 return -1;804 }805 if (!PyUnicode_Check(value)) {806 PyErr_SetString(PyExc_TypeError,807 "The first attribute value must be a string");808 return -1;809 }810 tmp = self->first;811 Py_INCREF(value);812 self->first = value;813 Py_DECREF(tmp);814 return 0;815}816```817818Функция получения принимает объект `Custom` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это может быть использовано, например, для создания единого набора функций получения и установки, которые решают, какой атрибут получить или установить на основе данных в замыкании.)819820Функция-сеттер получает объект `Custom`, новое значение и замыкание. Новое значение может быть *NULL*; в этом случае атрибут удаляется. В нашем сеттере мы вызываем ошибку, если атрибут удаляется или если его новое значение не является строкой.821822Мы создаём массив структур [`PyGetSetDef`](https://python-all.ru/3.6/c-api/structures.html#c.PyGetSetDef):823824```c825static PyGetSetDef Custom_getsetters[] = {826 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,827 "first name", NULL},828 {"last", (getter) Custom_getlast, (setter) Custom_setlast,829 "last name", NULL},830 {NULL} /* Страж */831};832```833834и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_getset):835836```c837.tp_getset = Custom_getsetters,838```839840Последним элементом в структуре [`PyGetSetDef`](https://python-all.ru/3.6/c-api/structures.html#c.PyGetSetDef) является «замыкание», упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаем *NULL*.841842Также удаляем определения членов для этих атрибутов:843844```c845static PyMemberDef Custom_members[] = {846 {"number", T_INT, offsetof(CustomObject, number), 0,847 "custom number"},848 {NULL} /* Страж */849};850```851852Также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [3](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id7):853854```c855static int856Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)857{858 static char *kwlist[] = {"first", "last", "number", NULL};859 PyObject *first = NULL, *last = NULL, *tmp;860861 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,862 &first, &last,863 &self->number))864 return -1;865866 if (first) {867 tmp = self->first;868 Py_INCREF(first);869 self->first = first;870 Py_DECREF(tmp);871 }872 if (last) {873 tmp = self->last;874 Py_INCREF(last);875 self->last = last;876 Py_DECREF(tmp);877 }878 return 0;879}880```881882С этими изменениями мы можем гарантировать, что члены `first` и `last` никогда не будут равны *NULL*, поэтому можно убрать проверки на значения *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_XDECREF) можно заменить на вызовы [`Py_DECREF()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_DECREF). Единственное место, где нельзя изменить эти вызовы, – это реализация `tp_dealloc`, где есть вероятность, что инициализация этих членов не удалась в `tp_new`.883884Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.885886## 2.4. Поддержка циклической сборки мусора887888Python имеет [циклический сборщик мусора (GC)](https://python-all.ru/3.6/glossary.html#term-garbage-collection), который может определять ненужные объекты даже при ненулевом счётчике ссылок. Это возможно, когда объекты образуют циклы. Например, рассмотрим:889890```pycon891>>> l = []892>>> l.append(l)893>>> del l894```895896В этом примере создаётся список, содержащий сам себя. При его удалении у него всё ещё остаётся ссылка на самого себя. Счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.897898Во второй версии примера `Custom` мы разрешили хранить объекты любого типа в атрибутах `first` или `last` [4](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id8). Кроме того, во второй и третьей версиях мы разрешили наследование от `Custom`, а подклассы могут добавлять произвольные атрибуты. По любой из этих двух причин объекты `Custom` могут участвовать в циклах:899900```pycon901>>> import custom3902>>> class Derived(custom3.Custom): pass903...904>>> n = Derived()905>>> n.some_attribute = n906```907908Чтобы экземпляр `Custom`, участвующий в цикле ссылок, был корректно обнаружен и собран циклическим GC, наш тип `Custom` должен заполнить два дополнительных слота и включить флаг, активирующий эти слоты:909910```c911#include <Python.h>912#include "structmember.h"913914typedef struct {915 PyObject_HEAD916 PyObject *first; /* имя */917 PyObject *last; /* фамилия */918 int number;919} CustomObject;920921static int922Custom_traverse(CustomObject *self, visitproc visit, void *arg)923{924 Py_VISIT(self->first);925 Py_VISIT(self->last);926 return 0;927}928929static int930Custom_clear(CustomObject *self)931{932 Py_CLEAR(self->first);933 Py_CLEAR(self->last);934 return 0;935}936937static void938Custom_dealloc(CustomObject *self)939{940 PyObject_GC_UnTrack(self);941 Custom_clear(self);942 Py_TYPE(self)->tp_free((PyObject *) self);943}944945static PyObject *946Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)947{948 CustomObject *self;949 self = (CustomObject *) type->tp_alloc(type, 0);950 if (self != NULL) {951 self->first = PyUnicode_FromString("");952 if (self->first == NULL) {953 Py_DECREF(self);954 return NULL;955 }956 self->last = PyUnicode_FromString("");957 if (self->last == NULL) {958 Py_DECREF(self);959 return NULL;960 }961 self->number = 0;962 }963 return (PyObject *) self;964}965966static int967Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)968{969 static char *kwlist[] = {"first", "last", "number", NULL};970 PyObject *first = NULL, *last = NULL, *tmp;971972 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,973 &first, &last,974 &self->number))975 return -1;976977 if (first) {978 tmp = self->first;979 Py_INCREF(first);980 self->first = first;981 Py_DECREF(tmp);982 }983 if (last) {984 tmp = self->last;985 Py_INCREF(last);986 self->last = last;987 Py_DECREF(tmp);988 }989 return 0;990}991992static PyMemberDef Custom_members[] = {993 {"number", T_INT, offsetof(CustomObject, number), 0,994 "custom number"},995 {NULL} /* Страж */996};997998static PyObject *999Custom_getfirst(CustomObject *self, void *closure)1000{1001 Py_INCREF(self->first);1002 return self->first;1003}10041005static int1006Custom_setfirst(CustomObject *self, PyObject *value, void *closure)1007{1008 if (value == NULL) {1009 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");1010 return -1;1011 }1012 if (!PyUnicode_Check(value)) {1013 PyErr_SetString(PyExc_TypeError,1014 "The first attribute value must be a string");1015 return -1;1016 }1017 Py_INCREF(value);1018 Py_CLEAR(self->first);1019 self->first = value;1020 return 0;1021}10221023static PyObject *1024Custom_getlast(CustomObject *self, void *closure)1025{1026 Py_INCREF(self->last);1027 return self->last;1028}10291030static int1031Custom_setlast(CustomObject *self, PyObject *value, void *closure)1032{1033 if (value == NULL) {1034 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");1035 return -1;1036 }1037 if (!PyUnicode_Check(value)) {1038 PyErr_SetString(PyExc_TypeError,1039 "The last attribute value must be a string");1040 return -1;1041 }1042 Py_INCREF(value);1043 Py_CLEAR(self->last);1044 self->last = value;1045 return 0;1046}10471048static PyGetSetDef Custom_getsetters[] = {1049 {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,1050 "first name", NULL},1051 {"last", (getter) Custom_getlast, (setter) Custom_setlast,1052 "last name", NULL},1053 {NULL} /* Страж */1054};10551056static PyObject *1057Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))1058{1059 return PyUnicode_FromFormat("%S %S", self->first, self->last);1060}10611062static PyMethodDef Custom_methods[] = {1063 {"name", (PyCFunction) Custom_name, METH_NOARGS,1064 "Return the name, combining the first and last name"1065 },1066 {NULL} /* Страж */1067};10681069static PyTypeObject CustomType = {1070 PyVarObject_HEAD_INIT(NULL, 0)1071 .tp_name = "custom4.Custom",1072 .tp_doc = "Custom objects",1073 .tp_basicsize = sizeof(CustomObject),1074 .tp_itemsize = 0,1075 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1076 .tp_new = Custom_new,1077 .tp_init = (initproc) Custom_init,1078 .tp_dealloc = (destructor) Custom_dealloc,1079 .tp_traverse = (traverseproc) Custom_traverse,1080 .tp_clear = (inquiry) Custom_clear,1081 .tp_members = Custom_members,1082 .tp_methods = Custom_methods,1083 .tp_getset = Custom_getsetters,1084};10851086static PyModuleDef custommodule = {1087 PyModuleDef_HEAD_INIT,1088 .m_name = "custom4",1089 .m_doc = "Example module that creates an extension type.",1090 .m_size = -1,1091};10921093PyMODINIT_FUNC1094PyInit_custom4(void)1095{1096 PyObject *m;1097 if (PyType_Ready(&CustomType) < 0)1098 return NULL;10991100 m = PyModule_Create(&custommodule);1101 if (m == NULL)1102 return NULL;11031104 Py_INCREF(&CustomType);1105 PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);1106 return m;1107}1108```11091110Во-первых, метод обхода (traversal) сообщает циклическому GC о подобъектах, которые могут участвовать в циклах:11111112```c1113static int1114Custom_traverse(CustomObject *self, visitproc visit, void *arg)1115{1116 int vret;1117 if (self->first) {1118 vret = visit(self->first, arg);1119 if (vret != 0)1120 return vret;1121 }1122 if (self->last) {1123 vret = visit(self->last, arg);1124 if (vret != 0)1125 return vret;1126 }1127 return 0;1128}1129```11301131Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.11321133Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.6/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.6/c-api/gcsupport.html#c.Py_VISIT) можно минимизировать количество шаблонного кода в `Custom_traverse`:11341135```c1136static int1137Custom_traverse(CustomObject *self, visitproc visit, void *arg)1138{1139 Py_VISIT(self->first);1140 Py_VISIT(self->last);1141 return 0;1142}1143```11441145> **Примечание**1146>1147> Реализация [`tp_traverse`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна именовать свои аргументы именно как *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.6/c-api/gcsupport.html#c.Py_VISIT).11481149Во-вторых, нужно предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах:11501151```c1152static int1153Custom_clear(CustomObject *self)1154{1155 Py_CLEAR(self->first);1156 Py_CLEAR(self->last);1157 return 0;1158}1159```11601161Обратите внимание на использование макроса [`Py_CLEAR()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_CLEAR). Это рекомендуемый и безопасный способ очистки атрибутов данных произвольных типов с одновременным уменьшением их счетчиков ссылок. Если вместо этого вызвать [`Py_XDECREF()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_XDECREF) для атрибута перед установкой его в *NULL*, существует вероятность, что деструктор атрибута вызовет обратно код, который снова читает атрибут (*особенно* при наличии циклической ссылки).11621163> **Примечание**1164>1165> Можно эмулировать [`Py_CLEAR()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_CLEAR) следующим образом:1166>1167> ```c1168> PyObject *tmp;1169> tmp = self->first;1170> self->first = NULL;1171> Py_XDECREF(tmp);1172> ```1173>1174> Тем не менее, гораздо проще и менее чревато ошибками всегда использовать [`Py_CLEAR()`](https://python-all.ru/3.6/c-api/refcounting.html#c.Py_CLEAR) при удалении атрибута. Не пытайтесь микрооптимизировать в ущерб надёжности!11751176Деаллокатор `Custom_dealloc` может вызывать произвольный код при очистке атрибутов. Это означает, что циклический GC может быть запущен внутри функции. Поскольку GC предполагает, что счётчик ссылок не равен нулю, нужно отменить отслеживание объекта GC с помощью вызова [`PyObject_GC_UnTrack()`](https://python-all.ru/3.6/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой членов. Вот наша переработанная версия деаллокатора с использованием [`PyObject_GC_UnTrack()`](https://python-all.ru/3.6/c-api/gcsupport.html#c.PyObject_GC_UnTrack) и `Custom_clear`:11771178```c1179static void1180Custom_dealloc(CustomObject *self)1181{1182 PyObject_GC_UnTrack(self);1183 Custom_clear(self);1184 Py_TYPE(self)->tp_free((PyObject *) self);1185}1186```11871188Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.6/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:11891190```c1191.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,1192```11931194Вот, в общем-то, и всё. Если бы мы написали собственные обработчики [`tp_alloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_free), их пришлось бы доработать для поддержки циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.11951196## 2.5. Наследование от других типов11971198Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему [`PyTypeObject`](https://python-all.ru/3.6/c-api/type.html#c.PyTypeObject). Совместное использование структур [`PyTypeObject`](https://python-all.ru/3.6/c-api/type.html#c.PyTypeObject) между модулями расширений может быть затруднительным.11991200В этом примере мы создадим тип `SubList`, наследующий от встроенного типа [`list`](https://python-all.ru/3.6/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик:12011202```pycon1203>>> import sublist1204>>> s = sublist.SubList(range(3))1205>>> s.extend(s)1206>>> print(len(s))120761208>>> print(s.increment())120911210>>> print(s.increment())121121212```12131214```c1215#include <Python.h>12161217typedef struct {1218 PyListObject list;1219 int state;1220} SubListObject;12211222static PyObject *1223SubList_increment(SubListObject *self, PyObject *unused)1224{1225 self->state++;1226 return PyLong_FromLong(self->state);1227}12281229static PyMethodDef SubList_methods[] = {1230 {"increment", (PyCFunction) SubList_increment, METH_NOARGS,1231 PyDoc_STR("increment state counter")},1232 {NULL},1233};12341235static int1236SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1237{1238 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1239 return -1;1240 self->state = 0;1241 return 0;1242}12431244static PyTypeObject SubListType = {1245 PyVarObject_HEAD_INIT(NULL, 0)1246 .tp_name = "sublist.SubList",1247 .tp_doc = "SubList objects",1248 .tp_basicsize = sizeof(SubListObject),1249 .tp_itemsize = 0,1250 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,1251 .tp_init = (initproc) SubList_init,1252 .tp_methods = SubList_methods,1253};12541255static PyModuleDef sublistmodule = {1256 PyModuleDef_HEAD_INIT,1257 .m_name = "sublist",1258 .m_doc = "Example module that creates an extension type.",1259 .m_size = -1,1260};12611262PyMODINIT_FUNC1263PyInit_sublist(void)1264{1265 PyObject *m;1266 SubListType.tp_base = &PyList_Type;1267 if (PyType_Ready(&SubListType) < 0)1268 return NULL;12691270 m = PyModule_Create(&sublistmodule);1271 if (m == NULL)1272 return NULL;12731274 Py_INCREF(&SubListType);1275 PyModule_AddObject(m, "SubList", (PyObject *) &SubListType);1276 return m;1277}1278```12791280Как видите, исходный код очень похож на примеры `Custom` из предыдущих разделов. Разберём основные различия между ними.12811282```c1283typedef struct {1284 PyListObject list;1285 int state;1286} SubListObject;1287```12881289Основное отличие для объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.6/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.12901291Когда объект Python является экземпляром `SubList`, его указатель `PyObject *` можно безопасно приводить как к `PyListObject *`, так и к `SubListObject *`:12921293```c1294static int1295SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)1296{1297 if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)1298 return -1;1299 self->state = 0;1300 return 0;1301}1302```13031304Выше показано, как вызывать метод `__init__` базового типа.13051306Этот шаблон важен при написании типа с пользовательскими членами [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new) и [`tp_dealloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Обработчик [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new) не должен фактически выделять память для объекта с помощью своего [`tp_alloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_alloc), а должен позволить базовому классу обработать это, вызвав его собственный [`tp_new`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_new).13071308Структура [`PyTypeObject`](https://python-all.ru/3.6/c-api/type.html#c.PyTypeObject) поддерживает [`tp_base`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_base) указывающее конкретный базовый класс типа. Из-за проблем с кроссплатформенными компиляторами нельзя заполнить это поле напрямую ссылкой на [`PyList_Type`](https://python-all.ru/3.6/c-api/list.html#c.PyList_Type); это следует сделать позже, в функции инициализации модуля:13091310```c1311PyMODINIT_FUNC1312PyInit_sublist(void)1313{1314 PyObject* m;1315 SubListType.tp_base = &PyList_Type;1316 if (PyType_Ready(&SubListType) < 0)1317 return NULL;13181319 m = PyModule_Create(&sublistmodule);1320 if (m == NULL)1321 return NULL;13221323 Py_INCREF(&SubListType);1324 PyModule_AddObject(m, "SubList", (PyObject *) &SubListType);1325 return m;1326}1327```13281329Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании от существующего типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.13301331После этого вызов [`PyType_Ready()`](https://python-all.ru/3.6/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Custom`.13321333Сноски13341335**[1](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id1)**13361337Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой.13381339**[2](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id2)**13401341На это полагались в обработчике [`tp_dealloc`](https://python-all.ru/3.6/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) в данном примере, поскольку тип не поддерживает сборку мусора.13421343**[3](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id3)**13441345Теперь известно, что первый и последний элементы являются строками, поэтому можно было бы менее осторожно уменьшать их счётчики ссылок. Однако допускаются экземпляры подклассов строк. Даже если освобождение обычных строк не вызовет обратного вызова в наши объекты, нельзя гарантировать, что освобождение экземпляра подкласса строки не вызовет такого обратного вызова.13461347**[4](https://python-all.ru/3.6/extending/newtypes_tutorial.html#id4)**13481349Кроме того, даже если атрибуты ограничены экземплярами строк, пользователь может передать произвольные подклассы [`str`](https://python-all.ru/3.6/library/stdtypes.html#str) и тем самым всё равно создать циклические ссылки.1350