newtypes.md
1> **Источник:** https://python-all.ru/3.0/extending/newtypes.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Определение новых типов89Как упоминалось в предыдущей главе, Python позволяет разработчику модуля расширения определять новые типы, которыми можно манипулировать из кода Python, подобно строкам и спискам в ядре Python.1011Это несложно; код всех типов расширений следует определённому шаблону, но есть несколько деталей, которые необходимо понять, прежде чем приступать к работе.1213## Основы1415Среда выполнения Python рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/3.0/c-api/structures.html#PyObject). Объект [`PyObject`](https://python-all.ru/3.0/c-api/structures.html#PyObject) не является очень значительным – он содержит только счетчик ссылок и указатель на «объект типа» этого объекта. Именно здесь происходит основное действие; объект типа определяет, какие (C) функции вызываются, когда, например, ищется атрибут объекта или объект умножается на другой. Эти C-функции называются «методами типа», чтобы отличать их от таких вещей, как `[].append` (которые мы называем «методами объекта»).1617Итак, чтобы определить новый тип объекта, необходимо создать новый объект типа.1819Такие вещи лучше всего объяснять на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип:2021```c22#include <Python.h>2324typedef struct {25 PyObject_HEAD26 /* Сюда помещаются поля, специфичные для типа. */27} noddy_NoddyObject;2829static PyTypeObject noddy_NoddyType = {30 PyObject_HEAD_INIT(NULL)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, а именно счетчик ссылок и указатель на объект типа. Это поля, которые предоставляет макрос `PyObject_HEAD`. Причина использования макроса – стандартизация расположения и возможность включения специальных отладочных полей в отладочных сборках. Обратите внимание, что после макроса `PyObject_HEAD` точка с запятой не ставится; она уже включена в определение макроса. Следует остерегаться случайного добавления точки с запятой; это легко сделать по привычке, и ваш компилятор может не пожаловаться, но у другого, скорее всего, возникнет ошибка! (В Windows известно, что MSVC считает это ошибкой и отказывается компилировать код.)9192Для сравнения взгляните на соответствующее определение для стандартных чисел с плавающей запятой Python:9394```c95typedef struct {96 PyObject_HEAD97 double ob_fval;98} PyFloatObject;99```100101Далее переходим к самому главному – объекту типа.102103```c104static PyTypeObject noddy_NoddyType = {105 PyObject_HEAD_INIT(NULL)106 "noddy.Noddy", /* tp_name */107 sizeof(noddy_NoddyObject), /* tp_basicsize */108 0, /* tp_itemsize */109 0, /* tp_dealloc */110 0, /* tp_print */111 0, /* tp_getattr */112 0, /* tp_setattr */113 0, /* tp_reserved */114 0, /* tp_repr */115 0, /* tp_as_number */116 0, /* tp_as_sequence */117 0, /* tp_as_mapping */118 0, /* tp_hash */119 0, /* tp_call */120 0, /* tp_str */121 0, /* tp_getattro */122 0, /* tp_setattro */123 0, /* tp_as_buffer */124 Py_TPFLAGS_DEFAULT, /* tp_flags */125 "Noddy objects", /* tp_doc */126};127```128129Теперь, если вы заглянете в определение [`PyTypeObject`](https://python-all.ru/3.0/c-api/type.html#PyTypeObject) в `object.h`, то увидите, что оно содержит гораздо больше полей, чем показано выше. Остальные поля будут заполнены нулями компилятором C, и обычно их не указывают явно, если они не нужны.130131Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:132133```c134PyObject_HEAD_INIT(NULL)135```136137Эта строка – небольшой изъян; хотелось бы написать:138139```c140PyObject_HEAD_INIT(&PyType_Type)141```142143поскольку типом объекта типа является «type», но это не строго соответствует C и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready).144145```c146"noddy.Noddy", /* tp_name */147```148149Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:150151```c152>>> "" + noddy.new_noddy()153Traceback (most recent call last):154 File "<stdin>", line 1, in ?155TypeError: cannot add type "noddy.Noddy" to string156```157158Обратите внимание, что имя является точечным и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы устанавливаем имя типа `noddy.Noddy`.159160```c161sizeof(noddy_NoddyObject), /* tp_basicsize */162```163164Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New`](https://python-all.ru/3.0/c-api/allocation.html#PyObject_New).165166> **Примечание**167>168> Если вы хотите, чтобы ваш тип мог быть подклассом из Python, и ваш тип имеет такое же значение [`tp_basicsize`](https://python-all.ru/3.0/c-api/typeobj.html#tp_basicsize), как и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс Python вашего типа должен будет указывать ваш тип первым в своем `__bases__`, иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, убедившись, что ваш тип имеет большее значение [`tp_basicsize`](https://python-all.ru/3.0/c-api/typeobj.html#tp_basicsize), чем его базовый тип. В большинстве случаев это будет верно, потому что либо вашим базовым типом будет [`object`](https://python-all.ru/3.0/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.169170```c1710, /* tp_itemsize */172```173174Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.175176Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.0/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).177178```c179Py_TPFLAGS_DEFAULT, /* tp_flags */180```181182Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые текущей версией Python.183184Строку документации для типа мы указываем в [`tp_doc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_doc).185186```c187"Noddy objects", /* tp_doc */188```189190Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.191192Пока что всё, что нам нужно уметь делать, это создавать новые объекты `Noddy`. Чтобы включить создание объектов, нужно предоставить реализацию [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new). В данном случае можно просто использовать реализацию по умолчанию, предоставляемую функцией API [`PyType_GenericNew`](https://python-all.ru/3.0/c-api/type.html#PyType_GenericNew). Хотелось бы просто присвоить это слоту [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new), но из соображений переносимости это невозможно: на некоторых платформах или компиляторах нельзя статически инициализировать член структуры функцией, определённой в другом C-модуле. Поэтому присвоим слоту [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new) значение в функции инициализации модуля перед вызовом [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready):193194```c195noddy_NoddyType.tp_new = PyType_GenericNew;196if (PyType_Ready(&noddy_NoddyType) < 0)197 return;198```199200Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!201202Всё остальное в файле должно быть знакомо, за исключением некоторого кода в `PyInit_noddy`:203204```c205if (PyType_Ready(&noddy_NoddyType) < 0)206 return;207```208209Это инициализирует тип `Noddy`, заполняя ряд членов, включая [`ob_type`](https://python-all.ru/3.0/c-api/typeobj.html#ob_type), который изначально устанавливается в *NULL*.210211```c212PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);213```214215Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy`, вызывая класс `Noddy`:216217```c218>>> import noddy219>>> mynoddy = noddy.Noddy()220```221222Вот и всё! Осталось только собрать это; поместите приведённый выше код в файл с именем `noddy.c` и223224```c225from distutils.core import setup, Extension226setup(name="noddy", version="1.0",227 ext_modules=[Extension("noddy", ["noddy.c"])])228```229230в файл с именем `setup.py`; затем, набрав231232```c233$ python setup.py build234```235236в командной оболочке, вы получите файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поиграться с объектами Noddy.237238Было не так сложно, правда?239240Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.241242### Добавление данных и методов к базовому примеру243244Расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Создадим новый модуль `noddy2`, который добавляет эти возможности:245246```c247#include <Python.h>248#include "structmember.h"249250typedef struct {251 PyObject_HEAD252 PyObject *first; /* имя */253 PyObject *last; /* фамилия */254 int number;255} Noddy;256257static void258Noddy_dealloc(Noddy* self)259{260 Py_XDECREF(self->first);261 Py_XDECREF(self->last);262 Py_TYPE(self)->tp_free((PyObject*)self);263}264265static PyObject *266Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)267{268 Noddy *self;269270 self = (Noddy *)type->tp_alloc(type, 0);271 if (self != NULL) {272 self->first = PyUnicode_FromString("");273 if (self->first == NULL)274 {275 Py_DECREF(self);276 return NULL;277 }278 279 self->last = PyUnicode_FromString("");280 if (self->last == NULL)281 {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 static PyObject *format = NULL;335 PyObject *args, *result;336337 if (format == NULL) {338 format = PyUnicode_FromString("%s %s");339 if (format == NULL)340 return NULL;341 }342343 if (self->first == NULL) {344 PyErr_SetString(PyExc_AttributeError, "first");345 return NULL;346 }347348 if (self->last == NULL) {349 PyErr_SetString(PyExc_AttributeError, "last");350 return NULL;351 }352353 args = Py_BuildValue("OO", self->first, self->last);354 if (args == NULL)355 return NULL;356357 result = PyUnicode_Format(format, args);358 Py_DECREF(args);359 360 return result;361}362363static PyMethodDef Noddy_methods[] = {364 {"name", (PyCFunction)Noddy_name, METH_NOARGS,365 "Return the name, combining the first and last name"366 },367 {NULL} /* Страж */368};369370static PyTypeObject NoddyType = {371 PyObject_HEAD_INIT(NULL)372 "noddy.Noddy", /* tp_name */373 sizeof(Noddy), /* tp_basicsize */374 0, /* tp_itemsize */375 (destructor)Noddy_dealloc, /* tp_dealloc */376 0, /* tp_print */377 0, /* tp_getattr */378 0, /* tp_setattr */379 0, /* tp_reserved */380 0, /* tp_repr */381 0, /* tp_as_number */382 0, /* tp_as_sequence */383 0, /* tp_as_mapping */384 0, /* tp_hash */385 0, /* tp_call */386 0, /* tp_str */387 0, /* tp_getattro */388 0, /* tp_setattro */389 0, /* tp_as_buffer */390 Py_TPFLAGS_DEFAULT |391 Py_TPFLAGS_BASETYPE, /* tp_flags */392 "Noddy objects", /* tp_doc */393 0, /* tp_traverse */394 0, /* tp_clear */395 0, /* tp_richcompare */396 0, /* tp_weaklistoffset */397 0, /* tp_iter */398 0, /* tp_iternext */399 Noddy_methods, /* tp_methods */400 Noddy_members, /* tp_members */401 0, /* tp_getset */402 0, /* tp_base */403 0, /* tp_dict */404 0, /* tp_descr_get */405 0, /* tp_descr_set */406 0, /* tp_dictoffset */407 (initproc)Noddy_init, /* tp_init */408 0, /* tp_alloc */409 Noddy_new, /* tp_new */410};411412static PyModuleDef noddy2module = {413 PyModuleDef_HEAD_INIT,414 "noddy2",415 "Example module that creates an extension type.",416 -1,417 NULL, NULL, NULL, NULL, NULL418};419420PyMODINIT_FUNC421PyInit_noddy2(void) 422{423 PyObject* m;424425 if (PyType_Ready(&NoddyType) < 0)426 return NULL;427428 m = PyModule_Create(&noddy2module);429 if (m == NULL)430 return NULL;431432 Py_INCREF(&NoddyType);433 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);434 return m;435}436```437438Эта версия модуля содержит ряд изменений.439440Мы добавили дополнительный include:441442```c443#include "structmember.h"444```445446Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.447448Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа сокращено до `NoddyType`.449450Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* – это строки Python, содержащие имя и фамилию. Атрибут *number* – целое число.451452Структура объекта обновляется соответствующим образом:453454```c455typedef struct {456 PyObject_HEAD457 PyObject *first;458 PyObject *last;459 int number;460} Noddy;461```462463Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:464465```c466static void467Noddy_dealloc(Noddy* self)468{469 Py_XDECREF(self->first);470 Py_XDECREF(self->last);471 Py_TYPE(self)->tp_free((PyObject*)self);472}473```474475которое присваивается члену [`tp_dealloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_dealloc):476477```c478(destructor)Noddy_dealloc, /*tp_dealloc*/479```480481Этот метод уменьшает счётчики ссылок двух атрибутов Python. Здесь используется [`Py_XDECREF`](https://python-all.ru/3.0/c-api/refcounting.html#Py_XDECREF), потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.0/c-api/typeobj.html#tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, потому что объект может быть экземпляром подкласса.482483Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:484485```c486static PyObject *487Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)488{489 Noddy *self;490491 self = (Noddy *)type->tp_alloc(type, 0);492 if (self != NULL) {493 self->first = PyString_FromString("");494 if (self->first == NULL)495 {496 Py_DECREF(self);497 return NULL;498 }499500 self->last = PyString_FromString("");501 if (self->last == NULL)502 {503 Py_DECREF(self);504 return NULL;505 }506507 self->number = 0;508 }509510 return (PyObject *)self;511}512```513514и устанавливаем его в член [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new):515516```c517Noddy_new, /* tp_new */518```519520Член new отвечает за создание (в отличие от инициализации) объектов типа. Он доступен в Python как метод [`__new__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__new__). Подробное обсуждение метода [`__new__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__new__) приведено в статье «Unifying types and classes in Python». Одна из причин реализовать метод new – гарантировать начальные значения переменных экземпляра. В данном случае мы используем метод new, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, что начальные значения равны *NULL*, можно было бы использовать [`PyType_GenericNew`](https://python-all.ru/3.0/c-api/type.html#PyType_GenericNew) в качестве метода new, как и раньше. [`PyType_GenericNew`](https://python-all.ru/3.0/c-api/type.html#PyType_GenericNew) инициализирует все переменные-члены экземпляра значением *NULL*.521522Метод new – это статический метод, которому передаются тип, создаваемый экземпляр, и любые аргументы, переданные при вызове типа, и который возвращает созданный новый объект. Методы new всегда принимают позиционные и именованные аргументы, но часто игнорируют их, оставляя обработку аргументов методам инициализации. Обратите внимание: если тип поддерживает создание подклассов, переданный тип может не быть определяемым типом. Метод new вызывает слот tp\_alloc для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_alloc) самостоятельно. Вместо этого [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready) заполняет его, наследуя от базового класса, который по умолчанию является [`object`](https://python-all.ru/3.0/library/functions.html#object). Большинство типов используют выделение по умолчанию.523524> **Примечание**525>526> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new) базового типа или [`__new__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__new__)), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не сделать, подклассы вашего типа в Python, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.TypeError).)527528Мы предоставляем функцию инициализации:529530```c531static int532Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)533{534 PyObject *first=NULL, *last=NULL, *tmp;535536 static char *kwlist[] = {"first", "last", "number", NULL};537538 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,539 &first, &last,540 &self->number))541 return -1;542543 if (first) {544 tmp = self->first;545 Py_INCREF(first);546 self->first = first;547 Py_XDECREF(tmp);548 }549550 if (last) {551 tmp = self->last;552 Py_INCREF(last);553 self->last = last;554 Py_XDECREF(tmp);555 }556557 return 0;558}559```560561заполнив слот [`tp_init`](https://python-all.ru/3.0/c-api/typeobj.html#tp_init).562563```c564(initproc)Noddy_init, /* tp_init */565```566567Слот [`tp_init`](https://python-all.ru/3.0/c-api/typeobj.html#tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, нельзя гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке объектов и может быть переопределён. Наш инициализатор принимает аргументы для начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы.568569Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__init__) на наших объектах. Поэтому нужно быть особенно осторожными при присваивании новых значений. Например, можно было бы попытаться присвоить члену `first` значение так:570571```c572if (first) {573 Py_XDECREF(self->first);574 Py_INCREF(first);575 self->first = first;576}577```578579Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому он может быть объектом любого типа. У него может быть деструктор, который вызывает код, пытающийся обратиться к члену `first`. Чтобы быть предусмотрительными и защититься от такой возможности, мы почти всегда переприсваиваем члены перед уменьшением их счётчиков ссылок. Когда же этого делать не нужно?580581- когда точно известно, что счётчик ссылок больше 1582- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.0/extending/newtypes.html#id6) не вызовет никаких вызовов обратно в код нашего типа583- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.0/extending/newtypes.html#id7)584585Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:586587```c588static PyMemberDef Noddy_members[] = {589 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,590 "first name"},591 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,592 "last name"},593 {"number", T_INT, offsetof(Noddy, number), 0,594 "noddy number"},595 {NULL} /* Страж */596};597```598599и поместите определения в слот [`tp_members`](https://python-all.ru/3.0/c-api/typeobj.html#tp_members):600601```c602Noddy_members, /* tp_members */603```604605Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел «Generic Attribute Management» ниже.606607Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.608609Определяем один метод `name()`, который выводит имя объекта как объединение имени и фамилии.610611```c612static PyObject *613Noddy_name(Noddy* self)614{615 static PyObject *format = NULL;616 PyObject *args, *result;617618 if (format == NULL) {619 format = PyString_FromString("%s %s");620 if (format == NULL)621 return NULL;622 }623624 if (self->first == NULL) {625 PyErr_SetString(PyExc_AttributeError, "first");626 return NULL;627 }628629 if (self->last == NULL) {630 PyErr_SetString(PyExc_AttributeError, "last");631 return NULL;632 }633634 args = Py_BuildValue("OO", self->first, self->last);635 if (args == NULL)636 return NULL;637638 result = PyString_Format(format, args);639 Py_DECREF(args);640641 return result;642}643```644645Метод реализован как C-функция, которая принимает экземпляр `Noddy` (или подкласса `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр как первый аргумент. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в принятии кортежа позиционных аргументов или словаря именованных аргументов. Этот метод эквивалентен следующему методу Python:646647```c648def name(self):649 return "%s %s" % (self.first, self.last)650```651652Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше предотвратить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать, в следующем разделе.653654Теперь, когда мы определили метод, нужно создать массив определений методов:655656```c657static PyMethodDef Noddy_methods[] = {658 {"name", (PyCFunction)Noddy_name, METH_NOARGS,659 "Return the name, combining the first and last name"660 },661 {NULL} /* Страж */662};663```664665и назначаем их в слот [`tp_methods`](https://python-all.ru/3.0/c-api/typeobj.html#tp_methods):666667```c668Noddy_methods, /* tp_methods */669```670671Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.0/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаются аргументы.672673Наконец, сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы осторожно, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.0/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флагов нашего класса:674675```c676Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/677```678679Мы переименовываем `PyInit_noddy` в `PyInit_noddy2` и обновляем имя модуля в структуре `PyModuleDef`.680681Наконец, обновляем наш файл `setup.py` для сборки нового модуля:682683```c684from distutils.core import setup, Extension685setup(name="noddy", version="1.0",686 ext_modules=[687 Extension("noddy", ["noddy.c"]),688 Extension("noddy2", ["noddy2.c"]),689 ])690```691692### Более тонкое управление атрибутами данных693694В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.695696```c697#include <Python.h>698#include "structmember.h"699700typedef struct {701 PyObject_HEAD702 PyObject *first;703 PyObject *last;704 int number;705} Noddy;706707static void708Noddy_dealloc(Noddy* self)709{710 Py_XDECREF(self->first);711 Py_XDECREF(self->last);712 Py_TYPE(self)->tp_free((PyObject*)self);713}714715static PyObject *716Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)717{718 Noddy *self;719720 self = (Noddy *)type->tp_alloc(type, 0);721 if (self != NULL) {722 self->first = PyUnicode_FromString("");723 if (self->first == NULL)724 {725 Py_DECREF(self);726 return NULL;727 }728 729 self->last = PyUnicode_FromString("");730 if (self->last == NULL)731 {732 Py_DECREF(self);733 return NULL;734 }735736 self->number = 0;737 }738739 return (PyObject *)self;740}741742static int743Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)744{745 PyObject *first=NULL, *last=NULL, *tmp;746747 static char *kwlist[] = {"first", "last", "number", NULL};748749 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist, 750 &first, &last, 751 &self->number))752 return -1; 753754 if (first) {755 tmp = self->first;756 Py_INCREF(first);757 self->first = first;758 Py_DECREF(tmp);759 }760761 if (last) {762 tmp = self->last;763 Py_INCREF(last);764 self->last = last;765 Py_DECREF(tmp);766 }767768 return 0;769}770771static PyMemberDef Noddy_members[] = {772 {"number", T_INT, offsetof(Noddy, number), 0,773 "noddy number"},774 {NULL} /* Страж */775};776777static PyObject *778Noddy_getfirst(Noddy *self, void *closure)779{780 Py_INCREF(self->first);781 return self->first;782}783784static int785Noddy_setfirst(Noddy *self, PyObject *value, void *closure)786{787 if (value == NULL) {788 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");789 return -1;790 }791 792 if (! PyUnicode_Check(value)) {793 PyErr_SetString(PyExc_TypeError, 794 "The first attribute value must be a string");795 return -1;796 }797 798 Py_DECREF(self->first);799 Py_INCREF(value);800 self->first = value; 801802 return 0;803}804805static PyObject *806Noddy_getlast(Noddy *self, void *closure)807{808 Py_INCREF(self->last);809 return self->last;810}811812static int813Noddy_setlast(Noddy *self, PyObject *value, void *closure)814{815 if (value == NULL) {816 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");817 return -1;818 }819 820 if (! PyUnicode_Check(value)) {821 PyErr_SetString(PyExc_TypeError, 822 "The last attribute value must be a string");823 return -1;824 }825 826 Py_DECREF(self->last);827 Py_INCREF(value);828 self->last = value; 829830 return 0;831}832833static PyGetSetDef Noddy_getseters[] = {834 {"first", 835 (getter)Noddy_getfirst, (setter)Noddy_setfirst,836 "first name",837 NULL},838 {"last", 839 (getter)Noddy_getlast, (setter)Noddy_setlast,840 "last name",841 NULL},842 {NULL} /* Страж */843};844845static PyObject *846Noddy_name(Noddy* self)847{848 static PyObject *format = NULL;849 PyObject *args, *result;850851 if (format == NULL) {852 format = PyUnicode_FromString("%s %s");853 if (format == NULL)854 return NULL;855 }856857 args = Py_BuildValue("OO", self->first, self->last);858 if (args == NULL)859 return NULL;860861 result = PyUnicode_Format(format, args);862 Py_DECREF(args);863 864 return result;865}866867static PyMethodDef Noddy_methods[] = {868 {"name", (PyCFunction)Noddy_name, METH_NOARGS,869 "Return the name, combining the first and last name"870 },871 {NULL} /* Страж */872};873874static PyTypeObject NoddyType = {875 PyObject_HEAD_INIT(NULL)876 "noddy.Noddy", /* tp_name */877 sizeof(Noddy), /* tp_basicsize */878 0, /* tp_itemsize */879 (destructor)Noddy_dealloc, /* tp_dealloc */880 0, /* tp_print */881 0, /* tp_getattr */882 0, /* tp_setattr */883 0, /* tp_reserved */884 0, /* tp_repr */885 0, /* tp_as_number */886 0, /* tp_as_sequence */887 0, /* tp_as_mapping */888 0, /* tp_hash */889 0, /* tp_call */890 0, /* tp_str */891 0, /* tp_getattro */892 0, /* tp_setattro */893 0, /* tp_as_buffer */894 Py_TPFLAGS_DEFAULT |895 Py_TPFLAGS_BASETYPE, /* tp_flags */896 "Noddy objects", /* tp_doc */897 0, /* tp_traverse */898 0, /* tp_clear */899 0, /* tp_richcompare */900 0, /* tp_weaklistoffset */901 0, /* tp_iter */902 0, /* tp_iternext */903 Noddy_methods, /* tp_methods */904 Noddy_members, /* tp_members */905 Noddy_getseters, /* tp_getset */906 0, /* tp_base */907 0, /* tp_dict */908 0, /* tp_descr_get */909 0, /* tp_descr_set */910 0, /* tp_dictoffset */911 (initproc)Noddy_init, /* tp_init */912 0, /* tp_alloc */913 Noddy_new, /* tp_new */914};915916static PyModuleDef noddy3module = {917 PyModuleDef_HEAD_INIT,918 "noddy3",919 "Example module that creates an extension type.",920 -1,921 NULL, NULL, NULL, NULL, NULL922};923924PyMODINIT_FUNC925PyInit_noddy3(void) 926{927 PyObject* m;928929 if (PyType_Ready(&NoddyType) < 0)930 return NULL;931932 m = PyModule_Create(&noddy3module);933 if (m == NULL)934 return NULL;935936 Py_INCREF(&NoddyType);937 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);938 return m;939}940```941942Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:943944```c945Noddy_getfirst(Noddy *self, void *closure)946{947 Py_INCREF(self->first);948 return self->first;949}950951static int952Noddy_setfirst(Noddy *self, PyObject *value, void *closure)953{954 if (value == NULL) {955 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");956 return -1;957 }958959 if (! PyString_Check(value)) {960 PyErr_SetString(PyExc_TypeError,961 "The first attribute value must be a string");962 return -1;963 }964965 Py_DECREF(self->first);966 Py_INCREF(value);967 self->first = value;968969 return 0;970}971```972973Функции получения передаётся объект `Noddy` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это, например, можно использовать, чтобы позволить единому набору функций получения и установки решать, какой атрибут получать или устанавливать, на основе данных в замыкании.)974975Функции установки передаётся объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.976977Создаём массив структур `PyGetSetDef`:978979```c980static PyGetSetDef Noddy_getseters[] = {981 {"first",982 (getter)Noddy_getfirst, (setter)Noddy_setfirst,983 "first name",984 NULL},985 {"last",986 (getter)Noddy_getlast, (setter)Noddy_setlast,987 "last name",988 NULL},989 {NULL} /* Страж */990};991```992993и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.0/c-api/typeobj.html#tp_getset):994995```c996Noddy_getseters, /* tp_getset */997```998999для регистрации геттеров и сеттеров наших атрибутов.10001001Последний элемент структуры `PyGetSetDef` – это упомянутое выше замыкание. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.10021003Также удаляем определения членов для этих атрибутов:10041005```c1006static PyMemberDef Noddy_members[] = {1007 {"number", T_INT, offsetof(Noddy, number), 0,1008 "noddy number"},1009 {NULL} /* Страж */1010};1011```10121013Также необходимо обновить обработчик [`tp_init`](https://python-all.ru/3.0/c-api/typeobj.html#tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.0/extending/newtypes.html#id8):10141015```c1016static int1017Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1018{1019 PyObject *first=NULL, *last=NULL, *tmp;10201021 static char *kwlist[] = {"first", "last", "number", NULL};10221023 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,1024 &first, &last,1025 &self->number))1026 return -1;10271028 if (first) {1029 tmp = self->first;1030 Py_INCREF(first);1031 self->first = first;1032 Py_DECREF(tmp);1033 }10341035 if (last) {1036 tmp = self->last;1037 Py_INCREF(last);1038 self->last = last;1039 Py_DECREF(tmp);1040 }10411042 return 0;1043}1044```10451046Благодаря этим изменениям можно гарантировать, что члены `first` и `last` никогда не будут *NULL*, так что проверки на *NULL* можно удалить почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF`](https://python-all.ru/3.0/c-api/refcounting.html#Py_XDECREF) можно заменить на [`Py_DECREF`](https://python-all.ru/3.0/c-api/refcounting.html#Py_DECREF). Единственное место, где эти вызовы нельзя изменить – деаллокатор, так как существует вероятность, что инициализация этих членов не удалась в конструкторе.10471048Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как мы делали ранее, и добавляем дополнительное определение в файл `setup.py`.10491050### Поддержка циклической сборки мусора10511052В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:10531054```c1055>>> l = []1056>>> l.append(l)1057>>> del l1058```10591060В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10611062Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.0/extending/newtypes.html#id9) Это означает, что объекты `Noddy` могут участвовать в циклах:10631064```c1065>>> import noddy21066>>> n = noddy2.Noddy()1067>>> l = [n]1068>>> n.first = l1069```10701071Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типам необходимо заполнить два слота и установить флаг класса, включающий эти слоты:10721073```c1074#include <Python.h>1075#include "structmember.h"10761077typedef struct {1078 PyObject_HEAD1079 PyObject *first;1080 PyObject *last;1081 int number;1082} Noddy;10831084static int1085Noddy_traverse(Noddy *self, visitproc visit, void *arg)1086{1087 int vret;10881089 if (self->first) {1090 vret = visit(self->first, arg);1091 if (vret != 0)1092 return vret;1093 }1094 if (self->last) {1095 vret = visit(self->last, arg);1096 if (vret != 0)1097 return vret;1098 }10991100 return 0;1101}11021103static int 1104Noddy_clear(Noddy *self)1105{1106 PyObject *tmp;11071108 tmp = self->first;1109 self->first = NULL;1110 Py_XDECREF(tmp);11111112 tmp = self->last;1113 self->last = NULL;1114 Py_XDECREF(tmp);11151116 return 0;1117}11181119static void1120Noddy_dealloc(Noddy* self)1121{1122 Noddy_clear(self);1123 Py_TYPE(self)->tp_free((PyObject*)self);1124}11251126static PyObject *1127Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1128{1129 Noddy *self;11301131 self = (Noddy *)type->tp_alloc(type, 0);1132 if (self != NULL) {1133 self->first = PyUnicode_FromString("");1134 if (self->first == NULL)1135 {1136 Py_DECREF(self);1137 return NULL;1138 }1139 1140 self->last = PyUnicode_FromString("");1141 if (self->last == NULL)1142 {1143 Py_DECREF(self);1144 return NULL;1145 }11461147 self->number = 0;1148 }11491150 return (PyObject *)self;1151}11521153static int1154Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1155{1156 PyObject *first=NULL, *last=NULL, *tmp;11571158 static char *kwlist[] = {"first", "last", "number", NULL};11591160 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, 1161 &first, &last, 1162 &self->number))1163 return -1; 11641165 if (first) {1166 tmp = self->first;1167 Py_INCREF(first);1168 self->first = first;1169 Py_XDECREF(tmp);1170 }11711172 if (last) {1173 tmp = self->last;1174 Py_INCREF(last);1175 self->last = last;1176 Py_XDECREF(tmp);1177 }11781179 return 0;1180}11811182static PyMemberDef Noddy_members[] = {1183 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1184 "first name"},1185 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1186 "last name"},1187 {"number", T_INT, offsetof(Noddy, number), 0,1188 "noddy number"},1189 {NULL} /* Страж */1190};11911192static PyObject *1193Noddy_name(Noddy* self)1194{1195 static PyObject *format = NULL;1196 PyObject *args, *result;11971198 if (format == NULL) {1199 format = PyUnicode_FromString("%s %s");1200 if (format == NULL)1201 return NULL;1202 }12031204 if (self->first == NULL) {1205 PyErr_SetString(PyExc_AttributeError, "first");1206 return NULL;1207 }12081209 if (self->last == NULL) {1210 PyErr_SetString(PyExc_AttributeError, "last");1211 return NULL;1212 }12131214 args = Py_BuildValue("OO", self->first, self->last);1215 if (args == NULL)1216 return NULL;12171218 result = PyUnicode_Format(format, args);1219 Py_DECREF(args);1220 1221 return result;1222}12231224static PyMethodDef Noddy_methods[] = {1225 {"name", (PyCFunction)Noddy_name, METH_NOARGS,1226 "Return the name, combining the first and last name"1227 },1228 {NULL} /* Страж */1229};12301231static PyTypeObject NoddyType = {1232 PyObject_HEAD_INIT(NULL)1233 "noddy.Noddy", /* tp_name */1234 sizeof(Noddy), /* tp_basicsize */1235 0, /* tp_itemsize */1236 (destructor)Noddy_dealloc, /* tp_dealloc */1237 0, /* tp_print */1238 0, /* tp_getattr */1239 0, /* tp_setattr */1240 0, /* tp_reserved */1241 0, /* tp_repr */1242 0, /* tp_as_number */1243 0, /* tp_as_sequence */1244 0, /* tp_as_mapping */1245 0, /* tp_hash */1246 0, /* tp_call */1247 0, /* tp_str */1248 0, /* tp_getattro */1249 0, /* tp_setattro */1250 0, /* tp_as_buffer */1251 Py_TPFLAGS_DEFAULT |1252 Py_TPFLAGS_BASETYPE |1253 Py_TPFLAGS_HAVE_GC, /* tp_flags */1254 "Noddy objects", /* tp_doc */1255 (traverseproc)Noddy_traverse, /* tp_traverse */1256 (inquiry)Noddy_clear, /* tp_clear */1257 0, /* tp_richcompare */1258 0, /* tp_weaklistoffset */1259 0, /* tp_iter */1260 0, /* tp_iternext */1261 Noddy_methods, /* tp_methods */1262 Noddy_members, /* tp_members */1263 0, /* tp_getset */1264 0, /* tp_base */1265 0, /* tp_dict */1266 0, /* tp_descr_get */1267 0, /* tp_descr_set */1268 0, /* tp_dictoffset */1269 (initproc)Noddy_init, /* tp_init */1270 0, /* tp_alloc */1271 Noddy_new, /* tp_new */1272};12731274static PyModuleDef noddy4module = {1275 PyModuleDef_HEAD_INIT,1276 "noddy4",1277 "Example module that creates an extension type.",1278 -1,1279 NULL, NULL, NULL, NULL, NULL1280};12811282PyMODINIT_FUNC1283PyInit_noddy4(void) 1284{1285 PyObject* m;12861287 if (PyType_Ready(&NoddyType) < 0)1288 return NULL;12891290 m = PyModule_Create(&noddy4module);1291 if (m == NULL)1292 return NULL;12931294 Py_INCREF(&NoddyType);1295 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1296 return m;1297}1298```12991300Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:13011302```c1303static int1304Noddy_traverse(Noddy *self, visitproc visit, void *arg)1305{1306 int vret;13071308 if (self->first) {1309 vret = visit(self->first, arg);1310 if (vret != 0)1311 return vret;1312 }1313 if (self->last) {1314 vret = visit(self->last, arg);1315 if (vret != 0)1316 return vret;1317 }13181319 return 0;1320}1321```13221323Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit`, которая передаётся методу обхода. Функция `visit` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно не равно нулю.13241325Python предоставляет макрос [`Py_VISIT`](https://python-all.ru/3.0/c-api/gcsupport.html#Py_VISIT), который автоматизирует вызов функций visit. С [`Py_VISIT`](https://python-all.ru/3.0/c-api/gcsupport.html#Py_VISIT) функцию `Noddy_traverse` можно упростить:13261327```c1328static int1329Noddy_traverse(Noddy *self, visitproc visit, void *arg)1330{1331 Py_VISIT(self->first);1332 Py_VISIT(self->last);1333 return 0;1334}1335```13361337> **Примечание**1338>1339> Обратите внимание, что в реализации [`tp_traverse`](https://python-all.ru/3.0/c-api/typeobj.html#tp_traverse) аргументы должны называться именно *visit* и *arg*, чтобы можно было использовать [`Py_VISIT`](https://python-all.ru/3.0/c-api/gcsupport.html#Py_VISIT). Это сделано для единообразия в этих скучных реализациях.13401341Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах. Реализуем этот метод и перереализуем деаллокатор для его использования:13421343```c1344static int1345Noddy_clear(Noddy *self)1346{1347 PyObject *tmp;13481349 tmp = self->first;1350 self->first = NULL;1351 Py_XDECREF(tmp);13521353 tmp = self->last;1354 self->last = NULL;1355 Py_XDECREF(tmp);13561357 return 0;1358}13591360static void1361Noddy_dealloc(Noddy* self)1362{1363 Noddy_clear(self);1364 Py_TYPE(self)->tp_free((PyObject*)self);1365}1366```13671368Обратите внимание на использование временной переменной в `Noddy_clear`. Временная переменная нужна, чтобы можно было установить каждый член в *NULL* перед уменьшением его счётчика ссылок. Это делается потому, что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может быть запущен код, который вызовет обращение к объекту. Кроме того, теперь, когда поддерживается сборка мусора, нужно также опасаться кода, который может запустить сборку мусора. Если сборка мусора будет запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.0/c-api/typeobj.html#tp_traverse). Нельзя допустить, чтобы `Noddy_traverse` был вызван, когда счётчик ссылок члена упал до нуля, а его значение ещё не установлено в *NULL*.13691370Python предоставляет макрос [`Py_CLEAR`](https://python-all.ru/3.0/c-api/refcounting.html#Py_CLEAR), который автоматизирует аккуратное уменьшение счётчиков ссылок. С [`Py_CLEAR`](https://python-all.ru/3.0/c-api/refcounting.html#Py_CLEAR) функцию `Noddy_clear` можно упростить:13711372```c1373static int1374Noddy_clear(Noddy *self)1375{1376 Py_CLEAR(self->first);1377 Py_CLEAR(self->last);1378 return 0;1379}1380```13811382Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.0/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13831384```c1385Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1386```13871388В общем, это всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_alloc) или [`tp_free`](https://python-all.ru/3.0/c-api/typeobj.html#tp_free), их нужно было бы изменить для циклической сборки мусора. Большинство расширений будут использовать автоматически предоставленные версии.13891390### Наследование от других типов13911392Можно создавать новые типы расширения, которые являются производными от существующих типов. Легче всего наследовать от встроенных типов, так как расширение может легко использовать необходимый ему [`PyTypeObject`](https://python-all.ru/3.0/c-api/type.html#PyTypeObject). Может быть сложно совместно использовать эти структуры [`PyTypeObject`](https://python-all.ru/3.0/c-api/type.html#PyTypeObject) между модулями расширения.13931394В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.0/library/functions.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, который увеличивает внутренний счётчик.13951396```c1397>>> import shoddy1398>>> s = shoddy.Shoddy(range(3))1399>>> s.extend(s)1400>>> print(len(s))140161402>>> print(s.increment())140311404>>> print(s.increment())140521406```14071408```c1409#include <Python.h>14101411typedef struct {1412 PyListObject list;1413 int state;1414} Shoddy;14151416static PyObject *1417Shoddy_increment(Shoddy *self, PyObject *unused)1418{1419 self->state++;1420 return PyLong_FromLong(self->state);1421}14221423static PyMethodDef Shoddy_methods[] = {1424 {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1425 PyDoc_STR("increment state counter")},1426 {NULL, NULL},1427};14281429static int1430Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1431{1432 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1433 return -1;1434 self->state = 0;1435 return 0;1436}14371438static PyTypeObject ShoddyType = {1439 PyObject_HEAD_INIT(NULL)1440 "shoddy.Shoddy", /* tp_name */1441 sizeof(Shoddy), /* tp_basicsize */1442 0, /* tp_itemsize */1443 0, /* tp_dealloc */1444 0, /* tp_print */1445 0, /* tp_getattr */1446 0, /* tp_setattr */1447 0, /* tp_reserved */1448 0, /* tp_repr */1449 0, /* tp_as_number */1450 0, /* tp_as_sequence */1451 0, /* tp_as_mapping */1452 0, /* tp_hash */1453 0, /* tp_call */1454 0, /* tp_str */1455 0, /* tp_getattro */1456 0, /* tp_setattro */1457 0, /* tp_as_buffer */1458 Py_TPFLAGS_DEFAULT |1459 Py_TPFLAGS_BASETYPE, /* tp_flags */1460 0, /* tp_doc */1461 0, /* tp_traverse */1462 0, /* tp_clear */1463 0, /* tp_richcompare */1464 0, /* tp_weaklistoffset */1465 0, /* tp_iter */1466 0, /* tp_iternext */1467 Shoddy_methods, /* tp_methods */1468 0, /* tp_members */1469 0, /* tp_getset */1470 0, /* tp_base */1471 0, /* tp_dict */1472 0, /* tp_descr_get */1473 0, /* tp_descr_set */1474 0, /* tp_dictoffset */1475 (initproc)Shoddy_init, /* tp_init */1476 0, /* tp_alloc */1477 0, /* tp_new */1478};14791480static PyModuleDef shoddymodule = {1481 PyModuleDef_HEAD_INIT,1482 "shoddy",1483 "Shoddy module",1484 -1,1485 NULL, NULL, NULL, NULL, NULL1486};14871488PyMODINIT_FUNC1489PyInit_shoddy(void)1490{1491 PyObject *m;14921493 ShoddyType.tp_base = &PyList_Type;1494 if (PyType_Ready(&ShoddyType) < 0)1495 return NULL;14961497 m = PyModule_Create(&shoddymodule);1498 if (m == NULL)1499 return NULL;15001501 Py_INCREF(&ShoddyType);1502 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1503}1504```15051506Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.15071508```c1509typedef struct {1510 PyListObject list;1511 int state;1512} Shoddy;1513```15141515Основное отличие для объектов производного типа заключается в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже будет включать [`PyObject_HEAD`](https://python-all.ru/3.0/c-api/structures.html#PyObject_HEAD) в начале своей структуры.15161517Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно приводить как к *PyListObject\**, так и к *Shoddy\**.15181519```c1520static int1521Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1522{1523 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1524 return -1;1525 self->state = 0;1526 return 0;1527}1528```15291530В методе `__init__` нашего типа видно, как вызвать метод `__init__` базового типа.15311532Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен непосредственно выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_alloc) – это будет сделано базовым классом при вызове его [`tp_new`](https://python-all.ru/3.0/c-api/typeobj.html#tp_new).15331534При заполнении [`PyTypeObject`](https://python-all.ru/3.0/c-api/type.html#PyTypeObject) для типа `Shoddy` есть слот для [`tp_base`](https://python-all.ru/3.0/c-api/typeobj.html#tp_base). Из-за проблем с межплатформенными компиляторами нельзя заполнить это поле напрямую [`PyList_Type`](https://python-all.ru/3.0/c-api/list.html#PyList_Type); это можно сделать позже в функции `init` модуля.15351536```c1537PyMODINIT_FUNC1538PyInit_shoddy(void)1539{1540 PyObject *m;15411542 ShoddyType.tp_base = &PyList_Type;1543 if (PyType_Ready(&ShoddyType) < 0)1544 return NULL;15451546 m = PyModule_Create(&shoddymodule);1547 if (m == NULL)1548 return NULL;15491550 Py_INCREF(&ShoddyType);1551 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1552}1553```15541555Перед вызовом [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready) слот [`tp_base`](https://python-all.ru/3.0/c-api/typeobj.html#tp_base) в структуре типа должен быть заполнен. При создании производного типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_alloc) значением [`PyType_GenericNew`](https://python-all.ru/3.0/c-api/type.html#PyType_GenericNew) – функция выделения памяти будет унаследована от базового типа.15561557После этого вызов [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready) и добавление объекта типа в модуль выполняются так же, как в базовых примерах с `Noddy`.15581559## Методы типа15601561Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.15621563Ниже приведено определение [`PyTypeObject`](https://python-all.ru/3.0/c-api/type.html#PyTypeObject), в котором опущены некоторые поля, используемые только в отладочных сборках:15641565```c1566typedef struct _typeobject {1567 PyObject_VAR_HEAD1568 char *tp_name; /* Для вывода в формате "<module>.<name>" */1569 int tp_basicsize, tp_itemsize; /* Для выделения памяти */15701571 /* Методы для реализации стандартных операций */15721573 destructor tp_dealloc;1574 printfunc tp_print;1575 getattrfunc tp_getattr;1576 setattrfunc tp_setattr;1577 void *tp_reserved;1578 reprfunc tp_repr;15791580 /* Наборы методов для стандартных классов */15811582 PyNumberMethods *tp_as_number;1583 PySequenceMethods *tp_as_sequence;1584 PyMappingMethods *tp_as_mapping;15851586 /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15871588 hashfunc tp_hash;1589 ternaryfunc tp_call;1590 reprfunc tp_str;1591 getattrofunc tp_getattro;1592 setattrofunc tp_setattro;15931594 /* Функции для доступа к объекту как к буферу ввода/вывода */1595 PyBufferProcs *tp_as_buffer;15961597 /* Флаги для определения наличия опциональных/расширенных возможностей */1598 long tp_flags;15991600 char *tp_doc; /* Строка документации */16011602 /* вызов функции для всех доступных объектов */1603 traverseproc tp_traverse;16041605 /* удаление ссылок на содержащиеся объекты */1606 inquiry tp_clear;16071608 /* расширенные сравнения */1609 richcmpfunc tp_richcompare;16101611 /* включение слабых ссылок */1612 long tp_weaklistoffset;16131614 /* Итераторы */1615 getiterfunc tp_iter;1616 iternextfunc tp_iternext;16171618 /* Дескриптор атрибутов и механизмы подклассов */1619 struct PyMethodDef *tp_methods;1620 struct PyMemberDef *tp_members;1621 struct PyGetSetDef *tp_getset;1622 struct _typeobject *tp_base;1623 PyObject *tp_dict;1624 descrgetfunc tp_descr_get;1625 descrsetfunc tp_descr_set;1626 long tp_dictoffset;1627 initproc tp_init;1628 allocfunc tp_alloc;1629 newfunc tp_new;1630 freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1631 inquiry tp_is_gc; /* Для PyObject_IS_GC */1632 PyObject *tp_bases;1633 PyObject *tp_mro; /* порядок разрешения методов */1634 PyObject *tp_cache;1635 PyObject *tp_subclasses;1636 PyObject *tp_weaklist;16371638} PyTypeObject;1639```16401641Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.16421643Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.16441645```c1646char *tp_name; /* Для вывода */1647```16481649Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!16501651```c1652int tp_basicsize, tp_itemsize; /* Для выделения памяти */1653```16541655Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов данного типа. В Python есть встроенная поддержка структур переменной длины (например, строки, списки), для которой предназначено поле [`tp_itemsize`](https://python-all.ru/3.0/c-api/typeobj.html#tp_itemsize). Об этом будет рассказано позже.16561657```c1658char *tp_doc;1659```16601661Сюда можно поместить строку (или её адрес), которая должна возвращаться, когда скрипт на Python обращается к `obj.__doc__` для получения строки документации.16621663Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16641665### Финализация и освобождение памяти16661667```c1668destructor tp_dealloc;1669```16701671Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа падает до нуля и интерпретатор Python хочет освободить его. Если у вашего типа есть память для освобождения или другие действия по очистке, поместите их сюда. Сам объект также должен быть освобождён здесь. Вот пример этой функции:16721673```c1674static void1675newdatatype_dealloc(newdatatypeobject * obj)1676{1677 free(obj->obj_UnderlyingDatatypePtr);1678 Py_TYPE(obj)->tp_free(obj);1679}1680```16811682Одно важное требование к функции деаллокатора – она не должна затрагивать отложенные исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при обычном возврате), не принимается никаких мер для защиты деаллокаторов от того, что исключение уже установлено. Любые действия деаллокатора, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к вводящим в заблуждение ошибкам интерпретатора. Правильный способ защиты – сохранить отложенное исключение перед выполнением опасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch`](https://python-all.ru/3.0/c-api/exceptions.html#PyErr_Fetch) и [`PyErr_Restore`](https://python-all.ru/3.0/c-api/exceptions.html#PyErr_Restore):16831684```c1685static void1686my_dealloc(PyObject *obj)1687{1688 MyObject *self = (MyObject *) obj;1689 PyObject *cbresult;16901691 if (self->my_callback != NULL) {1692 PyObject *err_type, *err_value, *err_traceback;1693 int have_error = PyErr_Occurred() ? 1 : 0;16941695 if (have_error)1696 PyErr_Fetch(&err_type, &err_value, &err_traceback);16971698 cbresult = PyObject_CallObject(self->my_callback, NULL);1699 if (cbresult == NULL)1700 PyErr_WriteUnraisable(self->my_callback);1701 else1702 Py_DECREF(cbresult);17031704 if (have_error)1705 PyErr_Restore(err_type, err_value, err_traceback);17061707 Py_DECREF(self->my_callback);1708 }1709 Py_TYPE(obj)->tp_free((PyObject*)self);1710}1711```17121713### Представление объекта17141715В Python есть два способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.0/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.0/library/functions.html#str). (Функция [`print()`](https://python-all.ru/3.0/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.0/library/functions.html#str).) Оба обработчика необязательны.17161717```c1718reprfunc tp_repr;1719reprfunc tp_str;1720```17211722Обработчик [`tp_repr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызывается. Вот простой пример:17231724```c1725static PyObject *1726newdatatype_repr(newdatatypeobject * obj)1727{1728 return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1729 obj->obj_UnderlyingDatatypePtr->size);1730}1731```17321733Если обработчик [`tp_repr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_repr) не указан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.0/c-api/typeobj.html#tp_name) типа и уникальный идентификатор объекта.17341735Обработчик [`tp_str`](https://python-all.ru/3.0/c-api/typeobj.html#tp_str) для [`str()`](https://python-all.ru/3.0/library/functions.html#str) – это то же самое, что описанный выше обработчик [`tp_repr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_repr) для [`repr()`](https://python-all.ru/3.0/library/functions.html#repr); то есть он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.0/library/functions.html#str) на экземпляре вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_repr), но результирующая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.0/c-api/typeobj.html#tp_str) не указан, вместо него используется обработчик [`tp_repr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_repr).17361737Вот простой пример:17381739```c1740static PyObject *1741newdatatype_str(newdatatypeobject * obj)1742{1743 return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",1744 obj->obj_UnderlyingDatatypePtr->size);1745}1746```17471748### Управление атрибутами17491750Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.17511752Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, достаточно реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.0/c-api/structures.html#PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.17531754```c1755getattrfunc tp_getattr; /* char * version */1756setattrfunc tp_setattr;1757/* ... */1758getattrofunc tp_getattro; /* PyObject * version */1759setattrofunc tp_setattro;1760```17611762Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют обобщённые реализации, которые можно использовать для предоставления версии функций управления атрибутами с [`PyObject*`](https://python-all.ru/3.0/c-api/structures.html#PyObject). Фактическая потребность в специфичных для типа обработчиках атрибутов почти полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового обобщённого механизма.17631764#### Общее управление атрибутами17651766Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:176717681. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready).17692. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17701771Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.17721773При вызове [`PyType_Ready`](https://python-all.ru/3.0/c-api/type.html#PyType_Ready) используются три таблицы, на которые ссылается объект типа, для создания [*дескрипторов*](https://python-all.ru/3.0/glossary.html#term-descriptor), которые помещаются в словарь объекта типа. Каждый дескриптор управляет доступом к одному атрибуту объекта-экземпляра. Каждая из таблиц необязательна; если все три равны *NULL*, экземпляры типа будут иметь только атрибуты, унаследованные от базового типа, и поля [`tp_getattro`](https://python-all.ru/3.0/c-api/typeobj.html#tp_getattro) и [`tp_setattro`](https://python-all.ru/3.0/c-api/typeobj.html#tp_setattro) также должны быть *NULL*, чтобы базовый тип обрабатывал атрибуты.17741775Таблицы объявлены как три поля объекта типа:17761777```c1778struct PyMethodDef *tp_methods;1779struct PyMemberDef *tp_members;1780struct PyGetSetDef *tp_getset;1781```17821783Если [`tp_methods`](https://python-all.ru/3.0/c-api/typeobj.html#tp_methods) не равен *NULL*, он должен указывать на массив структур [`PyMethodDef`](https://python-all.ru/3.0/c-api/structures.html#PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:17841785```c1786typedef struct PyMethodDef {1787 char *ml_name; /* имя метода */1788 PyCFunction ml_meth; /* функция реализации */1789 int ml_flags; /* флаги */1790 char *ml_doc; /* докстринг */1791} PyMethodDef;1792```17931794Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется ещё одна запись – это сигнальный элемент, обозначающий конец массива. Поле `ml_name` сигнального элемента должно быть равно *NULL*.17951796XXX Нужно сослаться на какое-то единое обсуждение полей структуры, общее со следующим разделом.17971798Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:17991800```c1801typedef struct PyMemberDef {1802 char *name;1803 int type;1804 int offset;1805 int flags;1806 char *doc;1807} PyMemberDef;1808```18091810Для каждой записи в таблице будет создан [*дескриптор*](https://python-all.ru/3.0/glossary.html#term-descriptor) и добавлен к типу; он сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.0/library/functions.html#type) должно содержать один из кодов типа, определённых в заголовочном файле `structmember.h`; это значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.18111812XXX Нужно перенести часть этого в общий раздел!18131814Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.18151816| Константа | Значение |1817| --- | --- |1818| `READONLY` | Никогда не доступен для записи. |1819| `RO` | Сокращение для `READONLY`. |1820| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1821| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1822| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |18231824Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.0/c-api/typeobj.html#tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.18251826Как и в таблице [`tp_methods`](https://python-all.ru/3.0/c-api/typeobj.html#tp_methods), требуется сигнальный элемент со значением `name` равным *NULL*.18271828#### Управление атрибутами, специфичное для типа18291830Для простоты здесь будет продемонстрирована только версия с `char*`; разница между версиями интерфейса с `char*` и [`PyObject*`](https://python-all.ru/3.0/c-api/structures.html#PyObject) заключается только в типе параметра name. Этот пример по сути делает то же самое, что и обобщённый пример выше, но не использует обобщённую поддержку, добавленную в Python 2.2. Он объясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, было понятно, что нужно делать.18311832Обработчик [`tp_getattr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, в которых вызывался бы метод [`__getattr__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__getattr__) класса.18331834Вот пример:18351836```c1837static PyObject *1838newdatatype_getattr(newdatatypeobject *obj, char *name)1839{1840 if (strcmp(name, "data") == 0)1841 {1842 return PyInt_FromLong(obj->data);1843 }18441845 PyErr_Format(PyExc_AttributeError,1846 "'%.50s' object has no attribute '%.400s'",1847 tp->tp_name, name);1848 return NULL;1849}1850```18511852Обработчик [`tp_setattr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_setattr) вызывается при вызове метода [`__setattr__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.0/reference/datamodel.html#object.__delattr__) экземпляра класса. Когда атрибут должен быть удалён, третий параметр будет равен *NULL*. Вот пример, который просто возбуждает исключение; если бы это действительно было всё, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.0/c-api/typeobj.html#tp_setattr) должен быть установлен в *NULL*.18531854```c1855static int1856newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1857{1858 (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1859 return -1;1860}1861```18621863### Поддержка абстрактных протоколов18641865Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [*Уровень абстрактных объектов*](https://python-all.ru/3.0/c-api/abstract.html#abstract).18661867Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)18681869```c1870PyNumberMethods tp_as_number;1871PySequenceMethods tp_as_sequence;1872PyMappingMethods tp_as_mapping;1873```18741875Если нужно, чтобы объект мог вести себя как число, последовательность или отображение, то следует поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.0/c-api/typeobj.html#PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.0/c-api/typeobj.html#PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.0/c-api/typeobj.html#PyMappingMethods) соответственно. Заполнить эту структуру подходящими значениями нужно самостоятельно. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.18761877```c1878hashfunc tp_hash;1879```18801881Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:18821883```c1884static long1885newdatatype_hash(newdatatypeobject *obj)1886{1887 long result;1888 result = obj->obj_UnderlyingDatatypePtr->size;1889 result = result * 3;1890 return result;1891}1892```18931894```c1895ternaryfunc tp_call;1896```18971898Эта функция вызывается, когда экземпляр вашего типа данных «вызывается». Например, если `obj1` – экземпляр вашего типа данных, а скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.0/c-api/typeobj.html#tp_call).18991900Эта функция принимает три аргумента:190119021. *arg1* – это экземпляр типа данных, который является субъектом вызова. Если вызовом является `obj1('hello')`, то *arg1* – это `obj1`.19032. *arg2* – это кортеж с аргументами вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple`](https://python-all.ru/3.0/c-api/arg.html#PyArg_ParseTuple).19043. *arg3* – это словарь переданных именованных аргументов. Если он не *NULL* и поддерживаются именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords`](https://python-all.ru/3.0/c-api/arg.html#PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если поддержка именованных аргументов не нужна и этот параметр не равен *NULL*, возбудите исключение [`TypeError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.TypeError) с сообщением о том, что именованные аргументы не поддерживаются.19051906Вот бессистемный пример реализации функции call.19071908```c1909/* Реализовать функцию вызова.1910 * obj1 – экземпляр, принимающий вызов.1911 * obj2 – кортеж, содержащий аргументы вызова, в данном1912 * случае 3 строки.1913 */1914static PyObject *1915newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1916{1917 PyObject *result;1918 char *arg1;1919 char *arg2;1920 char *arg3;19211922 if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1923 return NULL;1924 }1925 result = PyString_FromFormat(1926 "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1927 obj->obj_UnderlyingDatatypePtr->size,1928 arg1, arg2, arg3);1929 printf("\%s", PyString_AS_STRING(result));1930 return result;1931}1932```19331934XXX некоторые поля нужно добавить сюда...19351936```c1937/* Итераторы */1938getiterfunc tp_iter;1939iternextfunc tp_iternext;1940```19411942Эти функции обеспечивают поддержку протокола итератора. Любой объект, который должен поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика: `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.19431944Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), следует создавать и возвращать новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, а также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.19451946Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только один раз). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.0/library/exceptions.html#exceptions.StopIteration); пропуск исключения может дать несколько лучшую производительность. Если произошла реальная ошибка, он должен установить исключение и вернуть *NULL*.19471948### Поддержка слабых ссылок19491950Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).19511952Чтобы объект можно было использовать в слабых ссылках, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.0/c-api/structures.html#PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.0/c-api/typeobj.html#tp_weaklistoffset) соответствующего объекта типа в смещение этого поля. Например, тип экземпляра определён следующей структурой:19531954```c1955typedef struct {1956 PyObject_HEAD1957 PyClassObject *in_class; /* Объект класса */1958 PyObject *in_dict; /* Словарь */1959 PyObject *in_weakreflist; /* Список слабых ссылок */1960} PyInstanceObject;1961```19621963Статически объявленный объект типа для экземпляров определяется следующим образом:19641965```c1966PyTypeObject PyInstance_Type = {1967 PyObject_HEAD_INIT(&PyType_Type)1968 0,1969 "module.instance",19701971 /* Многое опущено для краткости... */19721973 Py_TPFLAGS_DEFAULT, /* tp_flags */1974 0, /* tp_doc */1975 0, /* tp_traverse */1976 0, /* tp_clear */1977 0, /* tp_richcompare */1978 offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */1979};1980```19811982Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:19831984```c1985static PyObject *1986instance_new() {1987 /* Прочие детали инициализации опущены для краткости */19881989 self->in_weakreflist = NULL;19901991 return (PyObject *) self;1992}1993```19941995Единственное дополнение состоит в том, что деструктор должен вызвать менеджер слабых ссылок, чтобы очистить любые слабые ссылки. Это должно быть сделано до того, как произойдет любая другая часть разрушения, но требуется только в том случае, если список слабых ссылок не равен *NULL*:19961997```c1998static void1999instance_dealloc(PyInstanceObject *inst)2000{2001 /* Выделить временные объекты, если нужно, но не начинать2002 уничтожение пока не начинать2003 */20042005 if (inst->in_weakreflist != NULL)2006 PyObject_ClearWeakRefs((PyObject *) inst);20072008 /* Продолжить уничтожение объекта обычным образом. */2009}2010```20112012### Дополнительные рекомендации20132014Помните, что большинство этих функций можно опустить; в этом случае нужно указать `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с дистрибутивом исходного кода Python.20152016Чтобы узнать, как реализовать какой-либо конкретный метод для вашего нового типа данных, сделайте следующее: скачайте и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем найдите в файлах C `tp_` плюс нужную функцию (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.20172018Когда требуется проверить, что объект является экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck`](https://python-all.ru/3.0/c-api/object.html#PyObject_TypeCheck). Пример её применения может выглядеть так:20192020```c2021if (! PyObject_TypeCheck(some_object, &MyType)) {2022 PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");2023 return NULL;2024}2025```20262027Сноски20282029| [\[1\]](https://python-all.ru/3.0/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2030| --- | --- |20312032| [\[2\]](https://python-all.ru/3.0/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.0/c-api/typeobj.html#tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы, которые позволяют «открепить» объект от сборки мусора, однако эти вызовы являются продвинутыми и не рассматриваются здесь. |2033| --- | --- |20342035| [\[3\]](https://python-all.ru/3.0/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2036| --- | --- |20372038| [\[4\]](https://python-all.ru/3.0/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2039| --- | --- |2040