newtypes.md
1> **Источник:** https://python-all.ru/3.5/extending/newtypes.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 2. Определение новых типов89Как упоминалось в предыдущей главе, Python позволяет разработчику модуля расширения определять новые типы, которыми можно манипулировать из кода Python, подобно строкам и спискам в ядре Python.1011Это несложно; код всех типов расширений следует определённому шаблону, но есть несколько деталей, которые необходимо понять, прежде чем приступать к работе.1213## 2.1. Основы1415Среда выполнения Python рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject), который служит «базовым типом» для всех объектов Python. [`PyObject`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject) сам содержит только счётчик ссылок и указатель на «объект типа» объекта. Именно здесь происходит основное действие: объект типа определяет, какие (C) функции вызываются, когда, например, у объекта запрашивается атрибут или он умножается на другой объект. Эти функции C называются «методами типа».1617Итак, чтобы определить новый тип объекта, необходимо создать новый объект типа.1819Такие вещи лучше всего объяснять на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип:2021```c22#include <Python.h>2324typedef struct {25 PyObject_HEAD26 /* Сюда помещаются поля, специфичные для типа. */27} noddy_NoddyObject;2829static PyTypeObject noddy_NoddyType = {30 PyVarObject_HEAD_INIT(NULL, 0)31 "noddy.Noddy", /* tp_name */32 sizeof(noddy_NoddyObject), /* tp_basicsize */33 0, /* tp_itemsize */34 0, /* tp_dealloc */35 0, /* tp_print */36 0, /* tp_getattr */37 0, /* tp_setattr */38 0, /* tp_reserved */39 0, /* tp_repr */40 0, /* tp_as_number */41 0, /* tp_as_sequence */42 0, /* tp_as_mapping */43 0, /* tp_hash */44 0, /* tp_call */45 0, /* tp_str */46 0, /* tp_getattro */47 0, /* tp_setattro */48 0, /* tp_as_buffer */49 Py_TPFLAGS_DEFAULT, /* tp_flags */50 "Noddy objects", /* tp_doc */51};5253static PyModuleDef noddymodule = {54 PyModuleDef_HEAD_INIT,55 "noddy",56 "Example module that creates an extension type.",57 -1,58 NULL, NULL, NULL, NULL, NULL59};6061PyMODINIT_FUNC62PyInit_noddy(void)63{64 PyObject* m;6566 noddy_NoddyType.tp_new = PyType_GenericNew;67 if (PyType_Ready(&noddy_NoddyType) < 0)68 return NULL;6970 m = PyModule_Create(&noddymodule);71 if (m == NULL)72 return NULL;7374 Py_INCREF(&noddy_NoddyType);75 PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);76 return m;77}78```7980Сразу это может показаться большим объёмом, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы.8182Первое, что будет новым:8384```c85typedef struct {86 PyObject_HEAD87} noddy_NoddyObject;88```8990Вот что будет содержать объект Noddy – в данном случае ничего сверх того, что содержит каждый объект Python: поле с именем `ob_base` типа [`PyObject`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject). [`PyObject`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject), в свою очередь, содержит поле `ob_refcnt` и указатель на объект типа. К ним можно обращаться с помощью макросов [`Py_REFCNT`](https://python-all.ru/3.5/c-api/structures.html#c.Py_REFCNT) и [`Py_TYPE`](https://python-all.ru/3.5/c-api/structures.html#c.Py_TYPE) соответственно. Это те поля, которые вводит макрос [`PyObject_HEAD`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject_HEAD). Макрос нужен для того, чтобы стандартизировать размещение и включить специальные отладочные поля в отладочных сборках.9192Обратите внимание, что после макроса [`PyObject_HEAD`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject_HEAD) точка с запятой не ставится; она уже включена в определение макроса. Будьте осторожны, чтобы случайно не добавить её; по привычке это легко сделать, и ваш компилятор, возможно, не выдаст ошибку, но чей-то другой может! (В Windows MSVC, как известно, считает это ошибкой и отказывается компилировать код.)9394Для сравнения взгляните на соответствующее определение для стандартных чисел с плавающей запятой Python:9596```c97typedef struct {98 PyObject_HEAD99 double ob_fval;100} PyFloatObject;101```102103Далее переходим к самому главному – объекту типа.104105```c106static PyTypeObject noddy_NoddyType = {107 PyVarObject_HEAD_INIT(NULL, 0)108 "noddy.Noddy", /* tp_name */109 sizeof(noddy_NoddyObject), /* tp_basicsize */110 0, /* tp_itemsize */111 0, /* tp_dealloc */112 0, /* tp_print */113 0, /* tp_getattr */114 0, /* tp_setattr */115 0, /* tp_as_async */116 0, /* tp_repr */117 0, /* tp_as_number */118 0, /* tp_as_sequence */119 0, /* tp_as_mapping */120 0, /* tp_hash */121 0, /* tp_call */122 0, /* tp_str */123 0, /* tp_getattro */124 0, /* tp_setattro */125 0, /* tp_as_buffer */126 Py_TPFLAGS_DEFAULT, /* tp_flags */127 "Noddy objects", /* tp_doc */128};129```130131Если теперь заглянуть в определение [`PyTypeObject`](https://python-all.ru/3.5/c-api/type.html#c.PyTypeObject) в `object.h`, можно увидеть, что у него гораздо больше полей, чем в приведённом выше определении. Остальные поля будут заполнены нулями компилятором C, и общепринятой практикой является не указывать их явно, если они не нужны.132133Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:134135```c136PyVarObject_HEAD_INIT(NULL, 0)137```138139Эта строка – небольшой изъян; хотелось бы написать:140141```c142PyVarObject_HEAD_INIT(&PyType_Type, 0)143```144145поскольку типом объекта типа является «type», но это не строго соответствует C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready).146147```c148"noddy.Noddy", /* tp_name */149```150151Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:152153```c154>>> "" + noddy.new_noddy()155Traceback (most recent call last):156 File "<stdin>", line 1, in <module>157TypeError: cannot add type "noddy.Noddy" to string158```159160Обратите внимание, что имя – это составное имя, включающее как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы задаём имя типа как `noddy.Noddy`. Один из побочных эффектов использования имени без точки состоит в том, что инструмент документации pydoc не будет отображать новый тип в документации модуля.161162```c163sizeof(noddy_NoddyObject), /* tp_basicsize */164```165166Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New()`](https://python-all.ru/3.5/c-api/allocation.html#c.PyObject_New).167168> **Примечание**169>170> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.5/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для [`tp_basicsize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.5/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.171172```c1730, /* tp_itemsize */174```175176Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.177178Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.5/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).179180```c181Py_TPFLAGS_DEFAULT, /* tp_flags */182```183184Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.185186Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_doc).187188```c189"Noddy objects", /* tp_doc */190```191192Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.193194Пока всё, что нам нужно уметь делать, – создавать новые объекты `Noddy`. Чтобы включить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new). В данном случае можно просто использовать реализацию по умолчанию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew). Хотелось бы просто присвоить это слоту [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new), но из соображений переносимости мы не можем статически инициализировать член структуры функцией, определённой в другом модуле C, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready):195196```c197noddy_NoddyType.tp_new = PyType_GenericNew;198if (PyType_Ready(&noddy_NoddyType) < 0)199 return;200```201202Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!203204Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_noddy()`:205206```c207if (PyType_Ready(&noddy_NoddyType) < 0)208 return;209```210211Это инициализирует тип `Noddy`, заполняя ряд членов, включая `ob_type`, который мы изначально установили в *NULL*.212213```c214PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);215```216217Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy` вызовом класса `Noddy`:218219```c220>>> import noddy221>>> mynoddy = noddy.Noddy()222```223224Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `noddy.c` и225226```c227from distutils.core import setup, Extension228setup(name="noddy", version="1.0",229 ext_modules=[Extension("noddy", ["noddy.c"])])230```231232в файл с именем `setup.py`; затем введите233234```console235$ python setup.py build236```237238в командной оболочке должен создать файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поэкспериментировать с объектами Noddy.239240Было не так сложно, правда?241242Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.243244### 2.1.1. Добавление данных и методов к базовому примеру245246Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `noddy2`, который добавляет эти возможности:247248```c249#include <Python.h>250#include "structmember.h"251252typedef struct {253 PyObject_HEAD254 PyObject *first; /* имя */255 PyObject *last; /* фамилия */256 int number;257} Noddy;258259static void260Noddy_dealloc(Noddy* self)261{262 Py_XDECREF(self->first);263 Py_XDECREF(self->last);264 Py_TYPE(self)->tp_free((PyObject*)self);265}266267static PyObject *268Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)269{270 Noddy *self;271272 self = (Noddy *)type->tp_alloc(type, 0);273 if (self != NULL) {274 self->first = PyUnicode_FromString("");275 if (self->first == NULL) {276 Py_DECREF(self);277 return NULL;278 }279280 self->last = PyUnicode_FromString("");281 if (self->last == NULL) {282 Py_DECREF(self);283 return NULL;284 }285286 self->number = 0;287 }288289 return (PyObject *)self;290}291292static int293Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)294{295 PyObject *first=NULL, *last=NULL, *tmp;296297 static char *kwlist[] = {"first", "last", "number", NULL};298299 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,300 &first, &last,301 &self->number))302 return -1;303304 if (first) {305 tmp = self->first;306 Py_INCREF(first);307 self->first = first;308 Py_XDECREF(tmp);309 }310311 if (last) {312 tmp = self->last;313 Py_INCREF(last);314 self->last = last;315 Py_XDECREF(tmp);316 }317318 return 0;319}320321static PyMemberDef Noddy_members[] = {322 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,323 "first name"},324 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,325 "last name"},326 {"number", T_INT, offsetof(Noddy, number), 0,327 "noddy number"},328 {NULL} /* Страж */329};330331static PyObject *332Noddy_name(Noddy* self)333{334 if (self->first == NULL) {335 PyErr_SetString(PyExc_AttributeError, "first");336 return NULL;337 }338339 if (self->last == NULL) {340 PyErr_SetString(PyExc_AttributeError, "last");341 return NULL;342 }343344 return PyUnicode_FromFormat("%S %S", self->first, self->last);345}346347static PyMethodDef Noddy_methods[] = {348 {"name", (PyCFunction)Noddy_name, METH_NOARGS,349 "Return the name, combining the first and last name"350 },351 {NULL} /* Страж */352};353354static PyTypeObject NoddyType = {355 PyVarObject_HEAD_INIT(NULL, 0)356 "noddy.Noddy", /* tp_name */357 sizeof(Noddy), /* tp_basicsize */358 0, /* tp_itemsize */359 (destructor)Noddy_dealloc, /* tp_dealloc */360 0, /* tp_print */361 0, /* tp_getattr */362 0, /* tp_setattr */363 0, /* tp_reserved */364 0, /* tp_repr */365 0, /* tp_as_number */366 0, /* tp_as_sequence */367 0, /* tp_as_mapping */368 0, /* tp_hash */369 0, /* tp_call */370 0, /* tp_str */371 0, /* tp_getattro */372 0, /* tp_setattro */373 0, /* tp_as_buffer */374 Py_TPFLAGS_DEFAULT |375 Py_TPFLAGS_BASETYPE, /* tp_flags */376 "Noddy objects", /* tp_doc */377 0, /* tp_traverse */378 0, /* tp_clear */379 0, /* tp_richcompare */380 0, /* tp_weaklistoffset */381 0, /* tp_iter */382 0, /* tp_iternext */383 Noddy_methods, /* tp_methods */384 Noddy_members, /* tp_members */385 0, /* tp_getset */386 0, /* tp_base */387 0, /* tp_dict */388 0, /* tp_descr_get */389 0, /* tp_descr_set */390 0, /* tp_dictoffset */391 (initproc)Noddy_init, /* tp_init */392 0, /* tp_alloc */393 Noddy_new, /* tp_new */394};395396static PyModuleDef noddy2module = {397 PyModuleDef_HEAD_INIT,398 "noddy2",399 "Example module that creates an extension type.",400 -1,401 NULL, NULL, NULL, NULL, NULL402};403404PyMODINIT_FUNC405PyInit_noddy2(void)406{407 PyObject* m;408409 if (PyType_Ready(&NoddyType) < 0)410 return NULL;411412 m = PyModule_Create(&noddy2module);413 if (m == NULL)414 return NULL;415416 Py_INCREF(&NoddyType);417 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);418 return m;419}420```421422Эта версия модуля содержит ряд изменений.423424Мы добавили дополнительный include:425426```c427#include <structmember.h>428```429430Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.431432Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа было сокращено до `NoddyType`.433434Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – целое число.435436Структура объекта обновляется соответствующим образом:437438```c439typedef struct {440 PyObject_HEAD441 PyObject *first;442 PyObject *last;443 int number;444} Noddy;445```446447Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:448449```c450static void451Noddy_dealloc(Noddy* self)452{453 Py_XDECREF(self->first);454 Py_XDECREF(self->last);455 Py_TYPE(self)->tp_free((PyObject*)self);456}457```458459который присваивается члену [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):460461```c462(destructor)Noddy_dealloc, /*tp_dealloc*/463```464465Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, поскольку объект может быть экземпляром подкласса.466467Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:468469```c470static PyObject *471Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)472{473 Noddy *self;474475 self = (Noddy *)type->tp_alloc(type, 0);476 if (self != NULL) {477 self->first = PyUnicode_FromString("");478 if (self->first == NULL) {479 Py_DECREF(self);480 return NULL;481 }482483 self->last = PyUnicode_FromString("");484 if (self->last == NULL) {485 Py_DECREF(self);486 return NULL;487 }488489 self->number = 0;490 }491492 return (PyObject *)self;493}494```495496и устанавливаем его в член [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new):497498```c499Noddy_new, /* tp_new */500```501502Новый метод отвечает за создание (в отличие от инициализации) объектов типа. Он доступен в Python как метод [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__). См. статью «Unifying types and classes in Python» для подробного обсуждения метода [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__). Одна из причин реализовать новый метод – гарантировать начальные значения переменных экземпляра. В данном случае мы используем новый метод, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew) в качестве нового метода, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.503504Новый метод – это статический метод, которому передаётся создаваемый тип и любые аргументы, переданные при вызове типа, и который возвращает созданный новый объект. Новые методы всегда принимают позиционные и именованные аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам. Обратите внимание: если тип поддерживает создание подклассов, переданный тип может не быть определяемым типом. Новый метод вызывает слот [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от нашего базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.5/library/functions.html#object). Большинство типов используют выделение памяти по умолчанию.505506> **Примечание**507>508> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) или [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__) базового типа), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов (MRO) во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы можете не создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.5/library/exceptions.html#TypeError).)509510Мы предоставляем функцию инициализации:511512```c513static int514Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)515{516 PyObject *first=NULL, *last=NULL, *tmp;517518 static char *kwlist[] = {"first", "last", "number", NULL};519520 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,521 &first, &last,522 &self->number))523 return -1;524525 if (first) {526 tmp = self->first;527 Py_INCREF(first);528 self->first = first;529 Py_XDECREF(tmp);530 }531532 if (last) {533 tmp = self->last;534 Py_INCREF(last);535 self->last = last;536 Py_XDECREF(tmp);537 }538539 return 0;540}541```542543заполняя слот [`tp_init`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_init).544545```c546(initproc)Noddy_init, /* tp_init */547```548549Слот [`tp_init`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от нового метода, мы не можем гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке (unpickling) объектов и может быть переопределён. Наш инициализатор принимает аргументы для задания начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы. Инициализаторы должны возвращать 0 в случае успеха или -1 при ошибке.550551Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__init__) наших объектов. По этой причине нужно быть особенно осторожным при присваивании новых значений. Может возникнуть искушение, например, присвоить члену `first` значение следующим образом:552553```c554if (first) {555 Py_XDECREF(self->first);556 Py_INCREF(first);557 self->first = first;558}559```560561Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому им может быть любой объект. У него может быть деструктор, вызывающий выполнение кода, который пытается получить доступ к члену `first`. Чтобы быть параноиками и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?562563- когда точно известно, что счётчик ссылок больше 1564- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.5/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа565- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.5/extending/newtypes.html#id8)566567Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:568569```c570static PyMemberDef Noddy_members[] = {571 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,572 "first name"},573 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,574 "last name"},575 {"number", T_INT, offsetof(Noddy, number), 0,576 "noddy number"},577 {NULL} /* Страж */578};579```580581и поместить определения в слот [`tp_members`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_members):582583```c584Noddy_members, /* tp_members */585```586587Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Управление атрибутами общего назначения](https://python-all.ru/3.5/extending/newtypes.html#generic-attribute-management) ниже.588589Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.590591Мы определяем один метод `name()`, который выводит имя объекта как конкатенацию имени и фамилии.592593```c594static PyObject *595Noddy_name(Noddy* self)596{597 if (self->first == NULL) {598 PyErr_SetString(PyExc_AttributeError, "first");599 return NULL;600 }601602 if (self->last == NULL) {603 PyErr_SetString(PyExc_AttributeError, "last");604 return NULL;605 }606607 return PyUnicode_FromFormat("%S %S", self->first, self->last);608}609```610611Метод реализован как функция на C, которая принимает экземпляр `Noddy` (или подкласс `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:612613```c614def name(self):615 return "%s %s" % (self.first, self.last)616```617618Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать в следующем разделе.619620Теперь, когда мы определили метод, нужно создать массив определений методов:621622```c623static PyMethodDef Noddy_methods[] = {624 {"name", (PyCFunction)Noddy_name, METH_NOARGS,625 "Return the name, combining the first and last name"626 },627 {NULL} /* Страж */628};629```630631и присваиваем их слоту [`tp_methods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_methods):632633```c634Noddy_methods, /* tp_methods */635```636637Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.5/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаётся аргументов.638639Наконец, мы сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы аккуратно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.5/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флага класса:640641```c642Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/643```644645Мы переименовываем `PyInit_noddy()` в `PyInit_noddy2()` и обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.5/c-api/module.html#c.PyModuleDef).646647Наконец, мы обновляем наш файл `setup.py`, чтобы собрать новый модуль:648649```c650from distutils.core import setup, Extension651setup(name="noddy", version="1.0",652 ext_modules=[653 Extension("noddy", ["noddy.c"]),654 Extension("noddy2", ["noddy2.c"]),655 ])656```657658### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных659660В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.661662```c663#include <Python.h>664#include "structmember.h"665666typedef struct {667 PyObject_HEAD668 PyObject *first;669 PyObject *last;670 int number;671} Noddy;672673static void674Noddy_dealloc(Noddy* self)675{676 Py_XDECREF(self->first);677 Py_XDECREF(self->last);678 Py_TYPE(self)->tp_free((PyObject*)self);679}680681static PyObject *682Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)683{684 Noddy *self;685686 self = (Noddy *)type->tp_alloc(type, 0);687 if (self != NULL) {688 self->first = PyUnicode_FromString("");689 if (self->first == NULL) {690 Py_DECREF(self);691 return NULL;692 }693694 self->last = PyUnicode_FromString("");695 if (self->last == NULL) {696 Py_DECREF(self);697 return NULL;698 }699700 self->number = 0;701 }702703 return (PyObject *)self;704}705706static int707Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)708{709 PyObject *first=NULL, *last=NULL, *tmp;710711 static char *kwlist[] = {"first", "last", "number", NULL};712713 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,714 &first, &last,715 &self->number))716 return -1;717718 if (first) {719 tmp = self->first;720 Py_INCREF(first);721 self->first = first;722 Py_DECREF(tmp);723 }724725 if (last) {726 tmp = self->last;727 Py_INCREF(last);728 self->last = last;729 Py_DECREF(tmp);730 }731732 return 0;733}734735static PyMemberDef Noddy_members[] = {736 {"number", T_INT, offsetof(Noddy, number), 0,737 "noddy number"},738 {NULL} /* Страж */739};740741static PyObject *742Noddy_getfirst(Noddy *self, void *closure)743{744 Py_INCREF(self->first);745 return self->first;746}747748static int749Noddy_setfirst(Noddy *self, PyObject *value, void *closure)750{751 if (value == NULL) {752 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");753 return -1;754 }755756 if (! PyUnicode_Check(value)) {757 PyErr_SetString(PyExc_TypeError,758 "The first attribute value must be a string");759 return -1;760 }761762 Py_DECREF(self->first);763 Py_INCREF(value);764 self->first = value;765766 return 0;767}768769static PyObject *770Noddy_getlast(Noddy *self, void *closure)771{772 Py_INCREF(self->last);773 return self->last;774}775776static int777Noddy_setlast(Noddy *self, PyObject *value, void *closure)778{779 if (value == NULL) {780 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");781 return -1;782 }783784 if (! PyUnicode_Check(value)) {785 PyErr_SetString(PyExc_TypeError,786 "The last attribute value must be a string");787 return -1;788 }789790 Py_DECREF(self->last);791 Py_INCREF(value);792 self->last = value;793794 return 0;795}796797static PyGetSetDef Noddy_getseters[] = {798 {"first",799 (getter)Noddy_getfirst, (setter)Noddy_setfirst,800 "first name",801 NULL},802 {"last",803 (getter)Noddy_getlast, (setter)Noddy_setlast,804 "last name",805 NULL},806 {NULL} /* Страж */807};808809static PyObject *810Noddy_name(Noddy* self)811{812 return PyUnicode_FromFormat("%S %S", self->first, self->last);813}814815static PyMethodDef Noddy_methods[] = {816 {"name", (PyCFunction)Noddy_name, METH_NOARGS,817 "Return the name, combining the first and last name"818 },819 {NULL} /* Страж */820};821822static PyTypeObject NoddyType = {823 PyVarObject_HEAD_INIT(NULL, 0)824 "noddy.Noddy", /* tp_name */825 sizeof(Noddy), /* tp_basicsize */826 0, /* tp_itemsize */827 (destructor)Noddy_dealloc, /* tp_dealloc */828 0, /* tp_print */829 0, /* tp_getattr */830 0, /* tp_setattr */831 0, /* tp_reserved */832 0, /* tp_repr */833 0, /* tp_as_number */834 0, /* tp_as_sequence */835 0, /* tp_as_mapping */836 0, /* tp_hash */837 0, /* tp_call */838 0, /* tp_str */839 0, /* tp_getattro */840 0, /* tp_setattro */841 0, /* tp_as_buffer */842 Py_TPFLAGS_DEFAULT |843 Py_TPFLAGS_BASETYPE, /* tp_flags */844 "Noddy objects", /* tp_doc */845 0, /* tp_traverse */846 0, /* tp_clear */847 0, /* tp_richcompare */848 0, /* tp_weaklistoffset */849 0, /* tp_iter */850 0, /* tp_iternext */851 Noddy_methods, /* tp_methods */852 Noddy_members, /* tp_members */853 Noddy_getseters, /* tp_getset */854 0, /* tp_base */855 0, /* tp_dict */856 0, /* tp_descr_get */857 0, /* tp_descr_set */858 0, /* tp_dictoffset */859 (initproc)Noddy_init, /* tp_init */860 0, /* tp_alloc */861 Noddy_new, /* tp_new */862};863864static PyModuleDef noddy3module = {865 PyModuleDef_HEAD_INIT,866 "noddy3",867 "Example module that creates an extension type.",868 -1,869 NULL, NULL, NULL, NULL, NULL870};871872PyMODINIT_FUNC873PyInit_noddy3(void)874{875 PyObject* m;876877 if (PyType_Ready(&NoddyType) < 0)878 return NULL;879880 m = PyModule_Create(&noddy3module);881 if (m == NULL)882 return NULL;883884 Py_INCREF(&NoddyType);885 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);886 return m;887}888```889890Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:891892```c893Noddy_getfirst(Noddy *self, void *closure)894{895 Py_INCREF(self->first);896 return self->first;897}898899static int900Noddy_setfirst(Noddy *self, PyObject *value, void *closure)901{902 if (value == NULL) {903 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");904 return -1;905 }906907 if (! PyUnicode_Check(value)) {908 PyErr_SetString(PyExc_TypeError,909 "The first attribute value must be a str");910 return -1;911 }912913 Py_DECREF(self->first);914 Py_INCREF(value);915 self->first = value;916917 return 0;918}919```920921Функция-getter получает объект `Noddy` и «замыкание» (closure), которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает продвинутое использование, при котором данные определения передаются getter'у и setter'у. Это может, например, использоваться, чтобы позволить одному набору функций getter и setter определять, какой атрибут получать или устанавливать, на основе данных в замыкании.)922923Функция-setter получает объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, и в этом случае атрибут удаляется. В нашем setter'е мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.924925Мы создаём массив структур `PyGetSetDef`:926927```c928static PyGetSetDef Noddy_getseters[] = {929 {"first",930 (getter)Noddy_getfirst, (setter)Noddy_setfirst,931 "first name",932 NULL},933 {"last",934 (getter)Noddy_getlast, (setter)Noddy_setlast,935 "last name",936 NULL},937 {NULL} /* Страж */938};939```940941и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_getset):942943```c944Noddy_getseters, /* tp_getset */945```946947для регистрации геттеров и сеттеров наших атрибутов.948949Последний элемент в структуре `PyGetSetDef` – это замыкание, упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.950951Также удаляем определения членов для этих атрибутов:952953```c954static PyMemberDef Noddy_members[] = {955 {"number", T_INT, offsetof(Noddy, number), 0,956 "noddy number"},957 {NULL} /* Страж */958};959```960961Нам также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передавать только строки [\[3\]](https://python-all.ru/3.5/extending/newtypes.html#id9):962963```c964static int965Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)966{967 PyObject *first=NULL, *last=NULL, *tmp;968969 static char *kwlist[] = {"first", "last", "number", NULL};970971 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,972 &first, &last,973 &self->number))974 return -1;975976 if (first) {977 tmp = self->first;978 Py_INCREF(first);979 self->first = first;980 Py_DECREF(tmp);981 }982983 if (last) {984 tmp = self->last;985 Py_INCREF(last);986 self->last = last;987 Py_DECREF(tmp);988 }989990 return 0;991}992```993994Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значение *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность, что инициализация этих членов завершилась неудачей в конструкторе.995996Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.997998### 2.1.3. Поддержка циклической сборки мусора9991000В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:10011002```c1003>>> l = []1004>>> l.append(l)1005>>> del l1006```10071008В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10091010Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.5/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:10111012```c1013>>> import noddy21014>>> n = noddy2.Noddy()1015>>> l = [n]1016>>> n.first = l1017```10181019Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типы должны заполнить два слота и установить классовый флаг, который включает эти слоты:10201021```c1022#include <Python.h>1023#include "structmember.h"10241025typedef struct {1026 PyObject_HEAD1027 PyObject *first;1028 PyObject *last;1029 int number;1030} Noddy;10311032static int1033Noddy_traverse(Noddy *self, visitproc visit, void *arg)1034{1035 int vret;10361037 if (self->first) {1038 vret = visit(self->first, arg);1039 if (vret != 0)1040 return vret;1041 }1042 if (self->last) {1043 vret = visit(self->last, arg);1044 if (vret != 0)1045 return vret;1046 }10471048 return 0;1049}10501051static int1052Noddy_clear(Noddy *self)1053{1054 PyObject *tmp;10551056 tmp = self->first;1057 self->first = NULL;1058 Py_XDECREF(tmp);10591060 tmp = self->last;1061 self->last = NULL;1062 Py_XDECREF(tmp);10631064 return 0;1065}10661067static void1068Noddy_dealloc(Noddy* self)1069{1070 PyObject_GC_UnTrack(self);1071 Noddy_clear(self);1072 Py_TYPE(self)->tp_free((PyObject*)self);1073}10741075static PyObject *1076Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1077{1078 Noddy *self;10791080 self = (Noddy *)type->tp_alloc(type, 0);1081 if (self != NULL) {1082 self->first = PyUnicode_FromString("");1083 if (self->first == NULL) {1084 Py_DECREF(self);1085 return NULL;1086 }10871088 self->last = PyUnicode_FromString("");1089 if (self->last == NULL) {1090 Py_DECREF(self);1091 return NULL;1092 }10931094 self->number = 0;1095 }10961097 return (PyObject *)self;1098}10991100static int1101Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1102{1103 PyObject *first=NULL, *last=NULL, *tmp;11041105 static char *kwlist[] = {"first", "last", "number", NULL};11061107 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,1108 &first, &last,1109 &self->number))1110 return -1;11111112 if (first) {1113 tmp = self->first;1114 Py_INCREF(first);1115 self->first = first;1116 Py_XDECREF(tmp);1117 }11181119 if (last) {1120 tmp = self->last;1121 Py_INCREF(last);1122 self->last = last;1123 Py_XDECREF(tmp);1124 }11251126 return 0;1127}11281129static PyMemberDef Noddy_members[] = {1130 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1131 "first name"},1132 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1133 "last name"},1134 {"number", T_INT, offsetof(Noddy, number), 0,1135 "noddy number"},1136 {NULL} /* Страж */1137};11381139static PyObject *1140Noddy_name(Noddy* self)1141{1142 if (self->first == NULL) {1143 PyErr_SetString(PyExc_AttributeError, "first");1144 return NULL;1145 }11461147 if (self->last == NULL) {1148 PyErr_SetString(PyExc_AttributeError, "last");1149 return NULL;1150 }11511152 return PyUnicode_FromFormat("%S %S", self->first, self->last);1153}11541155static PyMethodDef Noddy_methods[] = {1156 {"name", (PyCFunction)Noddy_name, METH_NOARGS,1157 "Return the name, combining the first and last name"1158 },1159 {NULL} /* Страж */1160};11611162static PyTypeObject NoddyType = {1163 PyVarObject_HEAD_INIT(NULL, 0)1164 "noddy.Noddy", /* tp_name */1165 sizeof(Noddy), /* tp_basicsize */1166 0, /* tp_itemsize */1167 (destructor)Noddy_dealloc, /* tp_dealloc */1168 0, /* tp_print */1169 0, /* tp_getattr */1170 0, /* tp_setattr */1171 0, /* tp_reserved */1172 0, /* tp_repr */1173 0, /* tp_as_number */1174 0, /* tp_as_sequence */1175 0, /* tp_as_mapping */1176 0, /* tp_hash */1177 0, /* tp_call */1178 0, /* tp_str */1179 0, /* tp_getattro */1180 0, /* tp_setattro */1181 0, /* tp_as_buffer */1182 Py_TPFLAGS_DEFAULT |1183 Py_TPFLAGS_BASETYPE |1184 Py_TPFLAGS_HAVE_GC, /* tp_flags */1185 "Noddy objects", /* tp_doc */1186 (traverseproc)Noddy_traverse, /* tp_traverse */1187 (inquiry)Noddy_clear, /* tp_clear */1188 0, /* tp_richcompare */1189 0, /* tp_weaklistoffset */1190 0, /* tp_iter */1191 0, /* tp_iternext */1192 Noddy_methods, /* tp_methods */1193 Noddy_members, /* tp_members */1194 0, /* tp_getset */1195 0, /* tp_base */1196 0, /* tp_dict */1197 0, /* tp_descr_get */1198 0, /* tp_descr_set */1199 0, /* tp_dictoffset */1200 (initproc)Noddy_init, /* tp_init */1201 0, /* tp_alloc */1202 Noddy_new, /* tp_new */1203};12041205static PyModuleDef noddy4module = {1206 PyModuleDef_HEAD_INIT,1207 "noddy4",1208 "Example module that creates an extension type.",1209 -1,1210 NULL, NULL, NULL, NULL, NULL1211};12121213PyMODINIT_FUNC1214PyInit_noddy4(void)1215{1216 PyObject* m;12171218 if (PyType_Ready(&NoddyType) < 0)1219 return NULL;12201221 m = PyModule_Create(&noddy4module);1222 if (m == NULL)1223 return NULL;12241225 Py_INCREF(&NoddyType);1226 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1227 return m;1228}1229```12301231Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:12321233```c1234static int1235Noddy_traverse(Noddy *self, visitproc visit, void *arg)1236{1237 int vret;12381239 if (self->first) {1240 vret = visit(self->first, arg);1241 if (vret != 0)1242 return vret;1243 }1244 if (self->last) {1245 vret = visit(self->last, arg);1246 if (vret != 0)1247 return vret;1248 }12491250 return 0;1251}1252```12531254Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.12551256Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.Py_VISIT), который автоматизирует вызов функций visit. С помощью [`Py_VISIT()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.Py_VISIT), `Noddy_traverse()` можно упростить:12571258```c1259static int1260Noddy_traverse(Noddy *self, visitproc visit, void *arg)1261{1262 Py_VISIT(self->first);1263 Py_VISIT(self->last);1264 return 0;1265}1266```12671268> **Примечание**1269>1270> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.Py_VISIT). Это делается для поощрения единообразия среди этих скучных реализаций.12711272Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах.12731274```c1275static int1276Noddy_clear(Noddy *self)1277{1278 PyObject *tmp;12791280 tmp = self->first;1281 self->first = NULL;1282 Py_XDECREF(tmp);12831284 tmp = self->last;1285 self->last = NULL;1286 Py_XDECREF(tmp);12871288 return 0;1289}1290```12911292Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы можно было установить каждый элемент в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это потому, что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратно объект. Кроме того, теперь, когда мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, запускающего сборку мусора. Если сборка мусора будет запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_traverse). Мы не можем рисковать тем, что функция `Noddy_traverse()` будет вызвана, когда счётчик ссылок элемента упал до нуля, а его значение ещё не установлено в *NULL*.12931294Python предоставляет функцию [`Py_CLEAR()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_CLEAR), которая автоматизирует аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_CLEAR) функцию `Noddy_clear()` можно упростить:12951296```c1297static int1298Noddy_clear(Noddy *self)1299{1300 Py_CLEAR(self->first);1301 Py_CLEAR(self->last);1302 return 0;1303}1304```13051306Обратите внимание, что функция `Noddy_dealloc()` может вызывать произвольные функции через метод `__del__` или колбэк слабой ссылки. Это означает, что циклическая сборка мусора может быть запущена внутри функции. Поскольку сборщик мусора предполагает, что счётчик ссылок не равен нулю, необходимо отменить отслеживание объекта сборщиком мусора, вызвав [`PyObject_GC_UnTrack()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой элементов. Вот переопределённый деаллокатор, который использует [`PyObject_GC_UnTrack()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.PyObject_GC_UnTrack) и `Noddy_clear()`.13071308```c1309static void1310Noddy_dealloc(Noddy* self)1311{1312 PyObject_GC_UnTrack(self);1313 Noddy_clear(self);1314 Py_TYPE(self)->tp_free((PyObject*)self);1315}1316```13171318Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.5/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13191320```c1321Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1322```13231324Вот в основном и всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_free), их нужно было бы модифицировать для циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.13251326### 2.1.4. Наследование от других типов13271328Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему `PyTypeObject`. Совместное использование структур `PyTypeObject` между модулями расширений может быть затруднительным.13291330В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.5/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.13311332```c1333>>> import shoddy1334>>> s = shoddy.Shoddy(range(3))1335>>> s.extend(s)1336>>> print(len(s))133761338>>> print(s.increment())133911340>>> print(s.increment())134121342```13431344```c1345#include <Python.h>13461347typedef struct {1348 PyListObject list;1349 int state;1350} Shoddy;13511352static PyObject *1353Shoddy_increment(Shoddy *self, PyObject *unused)1354{1355 self->state++;1356 return PyLong_FromLong(self->state);1357}13581359static PyMethodDef Shoddy_methods[] = {1360 {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1361 PyDoc_STR("increment state counter")},1362 {NULL, NULL},1363};13641365static int1366Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1367{1368 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1369 return -1;1370 self->state = 0;1371 return 0;1372}13731374static PyTypeObject ShoddyType = {1375 PyVarObject_HEAD_INIT(NULL, 0)1376 "shoddy.Shoddy", /* tp_name */1377 sizeof(Shoddy), /* tp_basicsize */1378 0, /* tp_itemsize */1379 0, /* tp_dealloc */1380 0, /* tp_print */1381 0, /* tp_getattr */1382 0, /* tp_setattr */1383 0, /* tp_reserved */1384 0, /* tp_repr */1385 0, /* tp_as_number */1386 0, /* tp_as_sequence */1387 0, /* tp_as_mapping */1388 0, /* tp_hash */1389 0, /* tp_call */1390 0, /* tp_str */1391 0, /* tp_getattro */1392 0, /* tp_setattro */1393 0, /* tp_as_buffer */1394 Py_TPFLAGS_DEFAULT |1395 Py_TPFLAGS_BASETYPE, /* tp_flags */1396 0, /* tp_doc */1397 0, /* tp_traverse */1398 0, /* tp_clear */1399 0, /* tp_richcompare */1400 0, /* tp_weaklistoffset */1401 0, /* tp_iter */1402 0, /* tp_iternext */1403 Shoddy_methods, /* tp_methods */1404 0, /* tp_members */1405 0, /* tp_getset */1406 0, /* tp_base */1407 0, /* tp_dict */1408 0, /* tp_descr_get */1409 0, /* tp_descr_set */1410 0, /* tp_dictoffset */1411 (initproc)Shoddy_init, /* tp_init */1412 0, /* tp_alloc */1413 0, /* tp_new */1414};14151416static PyModuleDef shoddymodule = {1417 PyModuleDef_HEAD_INIT,1418 "shoddy",1419 "Shoddy module",1420 -1,1421 NULL, NULL, NULL, NULL, NULL1422};14231424PyMODINIT_FUNC1425PyInit_shoddy(void)1426{1427 PyObject *m;14281429 ShoddyType.tp_base = &PyList_Type;1430 if (PyType_Ready(&ShoddyType) < 0)1431 return NULL;14321433 m = PyModule_Create(&shoddymodule);1434 if (m == NULL)1435 return NULL;14361437 Py_INCREF(&ShoddyType);1438 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1439 return m;1440}1441```14421443Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.14441445```c1446typedef struct {1447 PyListObject list;1448 int state;1449} Shoddy;1450```14511452Основное отличие объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже будет содержать [`PyObject_HEAD()`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.14531454Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно привести как к *PyListObject\**, так и к *Shoddy\**.14551456```c1457static int1458Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1459{1460 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1461 return -1;1462 self->state = 0;1463 return 0;1464}1465```14661467В методе `__init__` для нашего типа мы видим, как вызвать метод `__init__` базового типа.14681469Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен фактически выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) – это будет обработано базовым классом при вызове его метода [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new).14701471При заполнении структуры [`PyTypeObject()`](https://python-all.ru/3.5/c-api/type.html#c.PyTypeObject) для типа `Shoddy` вы видите слот для `tp_base()`. Из-за проблем с кроссплатформенными компиляторами нельзя заполнять это поле напрямую с помощью [`PyList_Type()`](https://python-all.ru/3.5/c-api/list.html#c.PyList_Type); это можно сделать позже в функции `init()` модуля.14721473```c1474PyMODINIT_FUNC1475PyInit_shoddy(void)1476{1477 PyObject *m;14781479 ShoddyType.tp_base = &PyList_Type;1480 if (PyType_Ready(&ShoddyType) < 0)1481 return NULL;14821483 m = PyModule_Create(&shoddymodule);1484 if (m == NULL)1485 return NULL;14861487 Py_INCREF(&ShoddyType);1488 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1489 return m;1490}1491```14921493Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании нового типа не обязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) с помощью [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.14941495После этого вызов [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Noddy`.14961497## 2.2. Методы типа14981499Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.15001501Здесь приводится определение [`PyTypeObject`](https://python-all.ru/3.5/c-api/type.html#c.PyTypeObject), некоторые поля, используемые только в отладочных сборках, опущены:15021503```c1504typedef struct _typeobject {1505 PyObject_VAR_HEAD1506 const char *tp_name; /* Для вывода в формате "<module>.<name>" */1507 Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */15081509 /* Методы для реализации стандартных операций */15101511 destructor tp_dealloc;1512 printfunc tp_print;1513 getattrfunc tp_getattr;1514 setattrfunc tp_setattr;1515 PyAsyncMethods *tp_as_async; /* ранее известный как tp_compare (Python 2)1516 или tp_reserved (Python 3) */1517 reprfunc tp_repr;15181519 /* Наборы методов для стандартных классов */15201521 PyNumberMethods *tp_as_number;1522 PySequenceMethods *tp_as_sequence;1523 PyMappingMethods *tp_as_mapping;15241525 /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15261527 hashfunc tp_hash;1528 ternaryfunc tp_call;1529 reprfunc tp_str;1530 getattrofunc tp_getattro;1531 setattrofunc tp_setattro;15321533 /* Функции для доступа к объекту как к буферу ввода/вывода */1534 PyBufferProcs *tp_as_buffer;15351536 /* Флаги для определения наличия опциональных/расширенных возможностей */1537 unsigned long tp_flags;15381539 const char *tp_doc; /* Строка документации */15401541 /* вызов функции для всех доступных объектов */1542 traverseproc tp_traverse;15431544 /* удаление ссылок на содержащиеся объекты */1545 inquiry tp_clear;15461547 /* расширенные сравнения */1548 richcmpfunc tp_richcompare;15491550 /* включение слабых ссылок */1551 Py_ssize_t tp_weaklistoffset;15521553 /* Итераторы */1554 getiterfunc tp_iter;1555 iternextfunc tp_iternext;15561557 /* Дескриптор атрибутов и механизмы подклассов */1558 struct PyMethodDef *tp_methods;1559 struct PyMemberDef *tp_members;1560 struct PyGetSetDef *tp_getset;1561 struct _typeobject *tp_base;1562 PyObject *tp_dict;1563 descrgetfunc tp_descr_get;1564 descrsetfunc tp_descr_set;1565 Py_ssize_t tp_dictoffset;1566 initproc tp_init;1567 allocfunc tp_alloc;1568 newfunc tp_new;1569 freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1570 inquiry tp_is_gc; /* Для PyObject_IS_GC */1571 PyObject *tp_bases;1572 PyObject *tp_mro; /* порядок разрешения методов */1573 PyObject *tp_cache;1574 PyObject *tp_subclasses;1575 PyObject *tp_weaklist;1576 destructor tp_del;15771578 /* Метка версии кэша атрибутов типа. Добавлено в версии 2.6. */1579 unsigned int tp_version_tag;15801581 destructor tp_finalize;15821583} PyTypeObject;1584```15851586Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.15871588Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.15891590```c1591const char *tp_name; /* Для вывода */1592```15931594Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!15951596```c1597Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */1598```15991600Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов этого типа. Python имеет встроенную поддержку структур переменной длины (например, строки, списки), и здесь появляется поле [`tp_itemsize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_itemsize). Это будет рассмотрено позже.16011602```c1603const char *tp_doc;1604```16051606Здесь можно указать строку (или её адрес), которая будет возвращена, когда скрипт Python обратится к `obj.__doc__` для получения строки документации.16071608Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16091610### 2.2.1. Финализация и освобождение памяти16111612```c1613destructor tp_dealloc;1614```16151616Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа уменьшается до нуля и интерпретатор Python хочет освободить его. Если ваш тип требует освобождения памяти или другой очистки, этот код можно разместить здесь. Сам объект также должен быть освобождён здесь. Вот пример такой функции:16171618```c1619static void1620newdatatype_dealloc(newdatatypeobject * obj)1621{1622 free(obj->obj_UnderlyingDatatypePtr);1623 Py_TYPE(obj)->tp_free(obj);1624}1625```16261627Важное требование к функции деаллокатора: она не должна вмешиваться в уже установленные исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при обычном возврате), ничего не делается для защиты деаллокаторов от того, что исключение уже установлено. Любые действия деаллокатора, которые могут вызвать выполнение дополнительного кода Python, могут обнаружить установленное исключение. Это может привести к вводящим в заблуждение ошибкам интерпретатора. Правильный способ защиты – сохранить текущее исключение перед выполнением небезопасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/3.5/c-api/exceptions.html#c.PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/3.5/c-api/exceptions.html#c.PyErr_Restore):16281629```c1630static void1631my_dealloc(PyObject *obj)1632{1633 MyObject *self = (MyObject *) obj;1634 PyObject *cbresult;16351636 if (self->my_callback != NULL) {1637 PyObject *err_type, *err_value, *err_traceback;16381639 /* Сохраняет текущее состояние исключения */1640 PyErr_Fetch(&err_type, &err_value, &err_traceback);16411642 cbresult = PyObject_CallObject(self->my_callback, NULL);1643 if (cbresult == NULL)1644 PyErr_WriteUnraisable(self->my_callback);1645 else1646 Py_DECREF(cbresult);16471648 /* Восстанавливает сохранённое состояние исключения */1649 PyErr_Restore(err_type, err_value, err_traceback);16501651 Py_DECREF(self->my_callback);1652 }1653 Py_TYPE(obj)->tp_free((PyObject*)self);1654}1655```16561657> **Примечание**1658>1659> Существуют ограничения на то, что можно безопасно делать в функции деаллокатора. Во-первых, если ваш тип поддерживает сборку мусора (с помощью [`tp_traverse`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_traverse) и/или [`tp_clear`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_clear)), некоторые члены объекта могут быть очищены или финализированы к моменту вызова [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Во-вторых, в [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) объект находится в нестабильном состоянии: его счётчик ссылок равен нулю. Любой вызов нетривиального объекта или API (как в примере выше) может снова вызвать [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), что приведёт к двойному освобождению и аварийному завершению.1660>1661> Начиная с Python 3.4, рекомендуется не помещать сложный код финализации в [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), а вместо этого использовать новый метод типа [`tp_finalize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_finalize).1662>1663> > **См. также**1664> >1665> > [**PEP 442**](https://python-all.ru/3.5/extending/newtypes.html) описывает новую схему финализации.16661667### 2.2.2. Представление объекта16681669В Python есть два способа создать текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.5/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str). (Функция [`print()`](https://python-all.ru/3.5/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str).) Оба этих обработчика необязательны.16701671```c1672reprfunc tp_repr;1673reprfunc tp_str;1674```16751676Обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызван. Вот простой пример:16771678```c1679static PyObject *1680newdatatype_repr(newdatatypeobject * obj)1681{1682 return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1683 obj->obj_UnderlyingDatatypePtr->size);1684}1685```16861687Если обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr) не задан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_name) типа и уникальный идентификатор объекта.16881689Обработчик [`tp_str`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_str) относится к [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str) так же, как описанный выше обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr) относится к [`repr()`](https://python-all.ru/3.5/library/functions.html#repr): он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str) для экземпляра вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr), но итоговая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_str) не указан, используется обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr).16901691Вот простой пример:16921693```c1694static PyObject *1695newdatatype_str(newdatatypeobject * obj)1696{1697 return PyUnicode_FromFormat("Stringified_newdatatype{{size:\%d}}",1698 obj->obj_UnderlyingDatatypePtr->size);1699}1700```17011702### 2.2.3. Управление атрибутами17031704Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.17051706Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, нужно реализовать функции только одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.17071708```c1709getattrfunc tp_getattr; /* char * version */1710setattrfunc tp_setattr;1711/* ... */1712getattrofunc tp_getattro; /* PyObject * version */1713setattrofunc tp_setattro;1714```17151716Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют универсальные реализации, которые можно использовать для предоставления версии [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject) функций управления атрибутами. Необходимость в специфических для типа обработчиках атрибутов практически полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового универсального механизма.17171718#### 2.2.3.1. Общее управление атрибутами17191720Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:172117221. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready).17232. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17241725Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.17261727Когда вызывается [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready), он использует три таблицы, на которые ссылается объект типа, для создания [дескрипторов](https://python-all.ru/3.5/glossary.html#term-descriptor), которые помещаются в словарь объекта типа. Каждый дескриптор управляет доступом к одному атрибуту объекта экземпляра. Каждая из таблиц необязательна; если все три равны *NULL*, экземпляры типа будут иметь только атрибуты, унаследованные от базового типа, и следует также установить поля [`tp_getattro`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_getattro) и [`tp_setattro`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_setattro) в *NULL*, позволяя базовому типу обрабатывать атрибуты.17281729Таблицы объявлены как три поля объекта типа:17301731```c1732struct PyMethodDef *tp_methods;1733struct PyMemberDef *tp_members;1734struct PyGetSetDef *tp_getset;1735```17361737Если [`tp_methods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_methods) не равен *NULL*, он должен ссылаться на массив структур [`PyMethodDef`](https://python-all.ru/3.5/c-api/structures.html#c.PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:17381739```c1740typedef struct PyMethodDef {1741 const char *ml_name; /* имя метода */1742 PyCFunction ml_meth; /* функция реализации */1743 int ml_flags; /* флаги */1744 const char *ml_doc; /* докстринг */1745} PyMethodDef;1746```17471748Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется одна дополнительная запись – это sentinel, отмечающий конец массива. Поле `ml_name` sentinel'а должно быть равно *NULL*.17491750Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:17511752```c1753typedef struct PyMemberDef {1754 char *name;1755 int type;1756 int offset;1757 int flags;1758 char *doc;1759} PyMemberDef;1760```17611762Для каждой записи в таблице будет создан [дескриптор](https://python-all.ru/3.5/glossary.html#term-descriptor) и добавлен к типу, который сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.5/library/functions.html#type) должно содержать один из кодов типов, определённых в заголовке `structmember.h`; значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.17631764Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.17651766| Константа | Значение |1767| --- | --- |1768| `READONLY` | Никогда не доступен для записи. |1769| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1770| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1771| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |17721773Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.17741775Как и в таблице [`tp_methods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_methods), требуется sentinel-запись со значением `name` равным *NULL*.17761777#### 2.2.3.2. Управление атрибутами, специфичное для типа17781779Для простоты здесь будет продемонстрирована только версия `char*`; тип параметра name – единственное различие между вариантами интерфейса `char*` и [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject). Этот пример делает то же самое, что и универсальный пример выше, но не использует универсальную поддержку, добавленную в Python 2.2. В нём объясняется, как вызываются функции-обработчики, чтобы, если понадобится расширить их функциональность, было понятно, что нужно сделать.17801781Обработчик [`tp_getattr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, что и метод [`__getattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattr__) класса.17821783Вот пример:17841785```c1786static PyObject *1787newdatatype_getattr(newdatatypeobject *obj, char *name)1788{1789 if (strcmp(name, "data") == 0)1790 {1791 return PyLong_FromLong(obj->data);1792 }17931794 PyErr_Format(PyExc_AttributeError,1795 "'%.50s' object has no attribute '%.400s'",1796 tp->tp_name, name);1797 return NULL;1798}1799```18001801Обработчик [`tp_setattr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_setattr) вызывается, когда вызывается метод [`__setattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__delattr__) экземпляра класса. Если атрибут должен быть удален, третий параметр будет равен *NULL*. Вот пример, который просто вызывает исключение; если бы это действительно было все, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_setattr) следовало бы установить в *NULL*.18021803```c1804static int1805newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1806{1807 (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1808 return -1;1809}1810```18111812### 2.2.4. Сравнение объектов18131814```c1815richcmpfunc tp_richcompare;1816```18171818Обработчик [`tp_richcompare`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_richcompare) вызывается, когда требуется сравнение. Он аналогичен [методам расширенного сравнения](https://python-all.ru/3.5/reference/datamodel.html#richcmpfuncs), таким как [`__lt__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__lt__), а также вызывается [`PyObject_RichCompare()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_RichCompare) и [`PyObject_RichCompareBool()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_RichCompareBool).18191820Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор – один из `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GT`, `Py_LT` или `Py_GT`. Она должна сравнить два объекта с помощью указанного оператора и вернуть `Py_True` или `Py_False`, если сравнение успешно, `Py_NotImplemented`, чтобы указать, что сравнение не реализовано и следует попробовать метод сравнения другого объекта, или *NULL*, если было установлено исключение.18211822Вот пример реализации для типа данных, который считается равным, если размер внутреннего указателя одинаков:18231824```c1825static PyObject *1826newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)1827{1828 PyObject *result;1829 int c, size1, size2;18301831 /* код для проверки, что оба аргумента имеют нужный тип1832 newdatatype опущен */18331834 size1 = obj1->obj_UnderlyingDatatypePtr->size;1835 size2 = obj2->obj_UnderlyingDatatypePtr->size;18361837 switch (op) {1838 case Py_LT: c = size1 < size2; break;1839 case Py_LE: c = size1 <= size2; break;1840 case Py_EQ: c = size1 == size2; break;1841 case Py_NE: c = size1 != size2; break;1842 case Py_GT: c = size1 > size2; break;1843 case Py_GE: c = size1 >= size2; break;1844 }1845 result = c ? Py_True : Py_False;1846 Py_INCREF(result);1847 return result;1848 }1849```18501851### 2.2.5. Поддержка абстрактных протоколов18521853Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [Уровень абстрактных объектов](https://python-all.ru/3.5/c-api/abstract.html#abstract).18541855Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)18561857```c1858PyNumberMethods *tp_as_number;1859PySequenceMethods *tp_as_sequence;1860PyMappingMethods *tp_as_mapping;1861```18621863Если требуется, чтобы объект мог вести себя как число, последовательность или отображение, нужно поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyMappingMethods) соответственно. Заполнять эту структуру подходящими значениями – ваша задача. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.18641865```c1866hashfunc tp_hash;1867```18681869Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:18701871```c1872static long1873newdatatype_hash(newdatatypeobject *obj)1874{1875 long result;1876 result = obj->obj_UnderlyingDatatypePtr->size;1877 result = result * 3;1878 return result;1879}1880```18811882```c1883ternaryfunc tp_call;1884```18851886Эта функция вызывается, когда экземпляр вашего типа данных «вызывается», например, если `obj1` является экземпляром вашего типа данных и скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_call).18871888Эта функция принимает три аргумента:188918901. *arg1* – это экземпляр типа данных, который является объектом вызова. Если вызов – `obj1('hello')`, то *arg1* равен `obj1`.18912. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/3.5/c-api/arg.html#c.PyArg_ParseTuple).18923. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/3.5/c-api/arg.html#c.PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите исключение [`TypeError`](https://python-all.ru/3.5/library/exceptions.html#TypeError) с сообщением о том, что именованные аргументы не поддерживаются.18931894Вот бессистемный пример реализации функции call.18951896```c1897/* Реализовать функцию вызова.1898 * obj1 – экземпляр, принимающий вызов.1899 * obj2 – кортеж, содержащий аргументы вызова, в данном1900 * случае 3 строки.1901 */1902static PyObject *1903newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1904{1905 PyObject *result;1906 char *arg1;1907 char *arg2;1908 char *arg3;19091910 if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1911 return NULL;1912 }1913 result = PyUnicode_FromFormat(1914 "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1915 obj->obj_UnderlyingDatatypePtr->size,1916 arg1, arg2, arg3);1917 return result;1918}1919```19201921```c1922/* Итераторы */1923getiterfunc tp_iter;1924iternextfunc tp_iternext;1925```19261927Эти функции обеспечивают поддержку протокола итератора. Любой объект, который хочет поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.19281929Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), должен создаваться и возвращаться новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, и также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.19301931Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только деструктивно). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.5/library/exceptions.html#StopIteration); отказ от исключения может дать несколько лучшую производительность. Если возникает реальная ошибка, он должен установить исключение и вернуть *NULL*.19321933### 2.2.6. Поддержка слабых ссылок19341935Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).19361937Чтобы объект мог быть слабо ссылаемым, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано значением *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_weaklistoffset) соответствующего объекта типа на смещение этого поля. Например, тип экземпляра определяется следующей структурой:19381939```c1940typedef struct {1941 PyObject_HEAD1942 PyClassObject *in_class; /* Объект класса */1943 PyObject *in_dict; /* Словарь */1944 PyObject *in_weakreflist; /* Список слабых ссылок */1945} PyInstanceObject;1946```19471948Статически объявленный объект типа для экземпляров определяется следующим образом:19491950```c1951PyTypeObject PyInstance_Type = {1952 PyVarObject_HEAD_INIT(&PyType_Type, 0)1953 0,1954 "module.instance",19551956 /* Многое опущено для краткости... */19571958 Py_TPFLAGS_DEFAULT, /* tp_flags */1959 0, /* tp_doc */1960 0, /* tp_traverse */1961 0, /* tp_clear */1962 0, /* tp_richcompare */1963 offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */1964};1965```19661967Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:19681969```c1970static PyObject *1971instance_new() {1972 /* Прочие детали инициализации опущены для краткости */19731974 self->in_weakreflist = NULL;19751976 return (PyObject *) self;1977}1978```19791980Единственное дополнение заключается в том, что деструктор должен вызывать менеджер слабых ссылок для очистки любых слабых ссылок. Это требуется только в том случае, если список слабых ссылок не равен *NULL*:19811982```c1983static void1984instance_dealloc(PyInstanceObject *inst)1985{1986 /* Выделить временные объекты, если нужно, но не начинать1987 уничтожение пока не начинать1988 */19891990 if (inst->in_weakreflist != NULL)1991 PyObject_ClearWeakRefs((PyObject *) inst);19921993 /* Продолжить уничтожение объекта обычным образом. */1994}1995```19961997### 2.2.7. Дополнительные рекомендации19981999Помните, что большинство этих функций можно опустить, в этом случае вы передаёте `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с исходным дистрибутивом Python.20002001Чтобы узнать, как реализовать определённый метод для нового типа данных, сделайте следующее: загрузите и распакуйте исходный дистрибутив Python. Перейдите в каталог `Objects`, затем найдите в C-файлах исходников `tp_` вместе с нужной функцией (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.20022003Когда требуется проверить, является ли объект экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_TypeCheck). Пример её использования может выглядеть так:20042005```c2006if (! PyObject_TypeCheck(some_object, &MyType)) {2007 PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");2008 return NULL;2009}2010```20112012Сноски20132014| [\[1\]](https://python-all.ru/3.5/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2015| --- | --- |20162017| [\[2\]](https://python-all.ru/3.5/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы для «открепления» объекта от сборки мусора, однако эти вызовы являются продвинутыми и здесь не рассматриваются. |2018| --- | --- |20192020| [\[3\]](https://python-all.ru/3.5/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2021| --- | --- |20222023| [\[4\]](https://python-all.ru/3.5/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2024| --- | --- |2025