newtypes.md
1> **Источник:** https://python-all.ru/3.4/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.4/c-api/structures.html#c.PyObject), который служит «базовым типом» для всех объектов Python. Сам [`PyObject`](https://python-all.ru/3.4/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: счётчик ссылок и указатель на объект типа. Эти поля предоставляет макрос `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 PyVarObject_HEAD_INIT(NULL, 0)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.4/c-api/type.html#c.PyTypeObject) в `object.h`, то увидите, что оно содержит гораздо больше полей, чем показано выше. Остальные поля будут заполнены нулями компилятором C, и обычно их не указывают явно, если они не нужны.130131Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:132133```c134PyVarObject_HEAD_INIT(NULL, 0)135```136137Эта строка – небольшой изъян; хотелось бы написать:138139```c140PyVarObject_HEAD_INIT(&PyType_Type, 0)141```142143поскольку типом объекта типа является «type», но это не совсем соответствует стандарту C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.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.4/c-api/allocation.html#c.PyObject_New).165166> **Примечание**167>168> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и при этом у вашего типа такое же значение [`tp_basicsize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), как у его базового типа, могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа в Python должен будет указывать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.4/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__) вашего типа без ошибки. Эту проблему можно избежать, если гарантировать, что значение [`tp_basicsize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа больше, чем у его базового типа. В большинстве случаев это и так верно, потому что либо ваш базовый тип – [`object`](https://python-all.ru/3.4/library/functions.html#object), либо вы добавляете поля данных к базовому типу и тем самым увеличиваете его размер.169170```c1710, /* tp_itemsize */172```173174Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.175176Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.4/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).177178```c179Py_TPFLAGS_DEFAULT, /* tp_flags */180```181182Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.183184Строку документации для типа мы указываем в [`tp_doc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_doc).185186```c187"Noddy objects", /* tp_doc */188```189190Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.191192Пока что всё, что нам нужно уметь делать – создавать новые объекты `Noddy`. Чтобы разрешить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new). В данном случае можно просто использовать стандартную реализацию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew). Хотелось бы просто присвоить её слоту [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new), но из соображений переносимости мы не можем этого сделать. На некоторых платформах или компиляторах нельзя статически инициализировать член структуры функцией, определённой в другом C-модуле, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.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`, который мы изначально установили в *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### 2.1.1. Добавление данных и методов к базовому примеру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 Py_DECREF(self);275 return NULL;276 }277278 self->last = PyUnicode_FromString("");279 if (self->last == NULL) {280 Py_DECREF(self);281 return NULL;282 }283284 self->number = 0;285 }286287 return (PyObject *)self;288}289290static int291Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)292{293 PyObject *first=NULL, *last=NULL, *tmp;294295 static char *kwlist[] = {"first", "last", "number", NULL};296297 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,298 &first, &last,299 &self->number))300 return -1;301302 if (first) {303 tmp = self->first;304 Py_INCREF(first);305 self->first = first;306 Py_XDECREF(tmp);307 }308309 if (last) {310 tmp = self->last;311 Py_INCREF(last);312 self->last = last;313 Py_XDECREF(tmp);314 }315316 return 0;317}318319static PyMemberDef Noddy_members[] = {320 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,321 "first name"},322 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,323 "last name"},324 {"number", T_INT, offsetof(Noddy, number), 0,325 "noddy number"},326 {NULL} /* Страж */327};328329static PyObject *330Noddy_name(Noddy* self)331{332 if (self->first == NULL) {333 PyErr_SetString(PyExc_AttributeError, "first");334 return NULL;335 }336337 if (self->last == NULL) {338 PyErr_SetString(PyExc_AttributeError, "last");339 return NULL;340 }341342 return PyUnicode_FromFormat("%S %S", self->first, self->last);343}344345static PyMethodDef Noddy_methods[] = {346 {"name", (PyCFunction)Noddy_name, METH_NOARGS,347 "Return the name, combining the first and last name"348 },349 {NULL} /* Страж */350};351352static PyTypeObject NoddyType = {353 PyVarObject_HEAD_INIT(NULL, 0)354 "noddy.Noddy", /* tp_name */355 sizeof(Noddy), /* tp_basicsize */356 0, /* tp_itemsize */357 (destructor)Noddy_dealloc, /* tp_dealloc */358 0, /* tp_print */359 0, /* tp_getattr */360 0, /* tp_setattr */361 0, /* tp_reserved */362 0, /* tp_repr */363 0, /* tp_as_number */364 0, /* tp_as_sequence */365 0, /* tp_as_mapping */366 0, /* tp_hash */367 0, /* tp_call */368 0, /* tp_str */369 0, /* tp_getattro */370 0, /* tp_setattro */371 0, /* tp_as_buffer */372 Py_TPFLAGS_DEFAULT |373 Py_TPFLAGS_BASETYPE, /* tp_flags */374 "Noddy objects", /* tp_doc */375 0, /* tp_traverse */376 0, /* tp_clear */377 0, /* tp_richcompare */378 0, /* tp_weaklistoffset */379 0, /* tp_iter */380 0, /* tp_iternext */381 Noddy_methods, /* tp_methods */382 Noddy_members, /* tp_members */383 0, /* tp_getset */384 0, /* tp_base */385 0, /* tp_dict */386 0, /* tp_descr_get */387 0, /* tp_descr_set */388 0, /* tp_dictoffset */389 (initproc)Noddy_init, /* tp_init */390 0, /* tp_alloc */391 Noddy_new, /* tp_new */392};393394static PyModuleDef noddy2module = {395 PyModuleDef_HEAD_INIT,396 "noddy2",397 "Example module that creates an extension type.",398 -1,399 NULL, NULL, NULL, NULL, NULL400};401402PyMODINIT_FUNC403PyInit_noddy2(void)404{405 PyObject* m;406407 if (PyType_Ready(&NoddyType) < 0)408 return NULL;409410 m = PyModule_Create(&noddy2module);411 if (m == NULL)412 return NULL;413414 Py_INCREF(&NoddyType);415 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);416 return m;417}418```419420Эта версия модуля содержит ряд изменений.421422Мы добавили дополнительный include:423424```c425#include <structmember.h>426```427428Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.429430Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа сокращено до `NoddyType`.431432Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* – это строки Python, содержащие имя и фамилию. Атрибут *number* – целое число.433434Структура объекта обновляется соответствующим образом:435436```c437typedef struct {438 PyObject_HEAD439 PyObject *first;440 PyObject *last;441 int number;442} Noddy;443```444445Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:446447```c448static void449Noddy_dealloc(Noddy* self)450{451 Py_XDECREF(self->first);452 Py_XDECREF(self->last);453 Py_TYPE(self)->tp_free((PyObject*)self);454}455```456457которое присваивается члену [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):458459```c460(destructor)Noddy_dealloc, /*tp_dealloc*/461```462463Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, потому что объект может быть экземпляром подкласса.464465Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:466467```c468static PyObject *469Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)470{471 Noddy *self;472473 self = (Noddy *)type->tp_alloc(type, 0);474 if (self != NULL) {475 self->first = PyUnicode_FromString("");476 if (self->first == NULL) {477 Py_DECREF(self);478 return NULL;479 }480481 self->last = PyUnicode_FromString("");482 if (self->last == NULL) {483 Py_DECREF(self);484 return NULL;485 }486487 self->number = 0;488 }489490 return (PyObject *)self;491}492```493494и устанавливаем его в член [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new):495496```c497Noddy_new, /* tp_new */498```499500Член new отвечает за создание (в отличие от инициализации) объектов типа. В Python он доступен как метод [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__). Подробное обсуждение метода [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__) приведено в статье «Unifying types and classes in Python». Одна из причин реализовать метод new – гарантировать начальные значения переменных экземпляра. В данном случае мы используем метод new, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew) в качестве метода new, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.501502Метод new – это статический метод, который получает тип, создаваемый в данный момент, и любые аргументы, переданные при вызове типа, и возвращает новый созданный объект. Методы new всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам инициализации. Обратите внимание: если тип поддерживает наследование, переданный тип может не быть тем типом, который определяется. Метод new вызывает слот [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) заполняет его, наследуя от нашего базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.4/library/functions.html#object). Большинство типов использует выделение по умолчанию.503504> **Примечание**505>506> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) базового типа или [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__)), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не сделать, подклассы вашего типа в Python, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.4/library/exceptions.html#TypeError).)507508Мы предоставляем функцию инициализации:509510```c511static int512Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)513{514 PyObject *first=NULL, *last=NULL, *tmp;515516 static char *kwlist[] = {"first", "last", "number", NULL};517518 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,519 &first, &last,520 &self->number))521 return -1;522523 if (first) {524 tmp = self->first;525 Py_INCREF(first);526 self->first = first;527 Py_XDECREF(tmp);528 }529530 if (last) {531 tmp = self->last;532 Py_INCREF(last);533 self->last = last;534 Py_XDECREF(tmp);535 }536537 return 0;538}539```540541заполнив слот [`tp_init`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_init).542543```c544(initproc)Noddy_init, /* tp_init */545```546547Слот [`tp_init`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, мы не можем гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке объектов и может быть переопределён. Наш инициализатор принимает аргументы для предоставления начальных значений экземпляра. Инициализаторы всегда принимают позиционные и ключевые аргументы. Инициализаторы должны возвращать 0 в случае успеха или -1 в случае ошибки.548549Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__init__) на наших объектах. Поэтому нужно быть особенно осторожными при присваивании новых значений. Например, можно было бы попытаться присвоить члену `first` значение так:550551```c552if (first) {553 Py_XDECREF(self->first);554 Py_INCREF(first);555 self->first = first;556}557```558559Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому он может быть объектом любого типа. У него может быть деструктор, который вызывает код, пытающийся обратиться к члену `first`. Чтобы быть предусмотрительными и защититься от такой возможности, мы почти всегда переприсваиваем члены перед уменьшением их счётчиков ссылок. Когда же этого делать не нужно?560561- когда точно известно, что счётчик ссылок больше 1562- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.4/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа563- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.4/extending/newtypes.html#id8)564565Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:566567```c568static PyMemberDef Noddy_members[] = {569 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,570 "first name"},571 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,572 "last name"},573 {"number", T_INT, offsetof(Noddy, number), 0,574 "noddy number"},575 {NULL} /* Страж */576};577```578579и поместите определения в слот [`tp_members`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_members):580581```c582Noddy_members, /* tp_members */583```584585Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [*Управление атрибутами общего назначения*](https://python-all.ru/3.4/extending/newtypes.html#generic-attribute-management) ниже.586587Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.588589Определяем один метод `name()`, который выводит имя объекта как объединение имени и фамилии.590591```c592static PyObject *593Noddy_name(Noddy* self)594{595 if (self->first == NULL) {596 PyErr_SetString(PyExc_AttributeError, "first");597 return NULL;598 }599600 if (self->last == NULL) {601 PyErr_SetString(PyExc_AttributeError, "last");602 return NULL;603 }604605 return PyUnicode_FromFormat("%S %S", self->first, self->last);606}607```608609Метод реализован как функция на C, которая принимает экземпляр `Noddy` (или подкласса `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в принятии кортежа позиционных аргументов или словаря именованных аргументов. Этот метод эквивалентен методу Python:610611```c612def name(self):613 return "%s %s" % (self.first, self.last)614```615616Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше предотвратить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать, в следующем разделе.617618Теперь, когда мы определили метод, нужно создать массив определений методов:619620```c621static PyMethodDef Noddy_methods[] = {622 {"name", (PyCFunction)Noddy_name, METH_NOARGS,623 "Return the name, combining the first and last name"624 },625 {NULL} /* Страж */626};627```628629и назначаем их в слот [`tp_methods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_methods):630631```c632Noddy_methods, /* tp_methods */633```634635Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.4/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаются аргументы.636637Наконец, сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы осторожно, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.4/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флагов нашего класса:638639```c640Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/641```642643Переименовываем `PyInit_noddy()` в `PyInit_noddy2()` и обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.4/c-api/module.html#c.PyModuleDef).644645Наконец, обновляем наш файл `setup.py` для сборки нового модуля:646647```c648from distutils.core import setup, Extension649setup(name="noddy", version="1.0",650 ext_modules=[651 Extension("noddy", ["noddy.c"]),652 Extension("noddy2", ["noddy2.c"]),653 ])654```655656### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных657658В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.659660```c661#include <Python.h>662#include "structmember.h"663664typedef struct {665 PyObject_HEAD666 PyObject *first;667 PyObject *last;668 int number;669} Noddy;670671static void672Noddy_dealloc(Noddy* self)673{674 Py_XDECREF(self->first);675 Py_XDECREF(self->last);676 Py_TYPE(self)->tp_free((PyObject*)self);677}678679static PyObject *680Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)681{682 Noddy *self;683684 self = (Noddy *)type->tp_alloc(type, 0);685 if (self != NULL) {686 self->first = PyUnicode_FromString("");687 if (self->first == NULL) {688 Py_DECREF(self);689 return NULL;690 }691692 self->last = PyUnicode_FromString("");693 if (self->last == NULL) {694 Py_DECREF(self);695 return NULL;696 }697698 self->number = 0;699 }700701 return (PyObject *)self;702}703704static int705Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)706{707 PyObject *first=NULL, *last=NULL, *tmp;708709 static char *kwlist[] = {"first", "last", "number", NULL};710711 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,712 &first, &last,713 &self->number))714 return -1;715716 if (first) {717 tmp = self->first;718 Py_INCREF(first);719 self->first = first;720 Py_DECREF(tmp);721 }722723 if (last) {724 tmp = self->last;725 Py_INCREF(last);726 self->last = last;727 Py_DECREF(tmp);728 }729730 return 0;731}732733static PyMemberDef Noddy_members[] = {734 {"number", T_INT, offsetof(Noddy, number), 0,735 "noddy number"},736 {NULL} /* Страж */737};738739static PyObject *740Noddy_getfirst(Noddy *self, void *closure)741{742 Py_INCREF(self->first);743 return self->first;744}745746static int747Noddy_setfirst(Noddy *self, PyObject *value, void *closure)748{749 if (value == NULL) {750 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");751 return -1;752 }753754 if (! PyUnicode_Check(value)) {755 PyErr_SetString(PyExc_TypeError,756 "The first attribute value must be a string");757 return -1;758 }759760 Py_DECREF(self->first);761 Py_INCREF(value);762 self->first = value;763764 return 0;765}766767static PyObject *768Noddy_getlast(Noddy *self, void *closure)769{770 Py_INCREF(self->last);771 return self->last;772}773774static int775Noddy_setlast(Noddy *self, PyObject *value, void *closure)776{777 if (value == NULL) {778 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");779 return -1;780 }781782 if (! PyUnicode_Check(value)) {783 PyErr_SetString(PyExc_TypeError,784 "The last attribute value must be a string");785 return -1;786 }787788 Py_DECREF(self->last);789 Py_INCREF(value);790 self->last = value;791792 return 0;793}794795static PyGetSetDef Noddy_getseters[] = {796 {"first",797 (getter)Noddy_getfirst, (setter)Noddy_setfirst,798 "first name",799 NULL},800 {"last",801 (getter)Noddy_getlast, (setter)Noddy_setlast,802 "last name",803 NULL},804 {NULL} /* Страж */805};806807static PyObject *808Noddy_name(Noddy* self)809{810 return PyUnicode_FromFormat("%S %S", self->first, self->last);811}812813static PyMethodDef Noddy_methods[] = {814 {"name", (PyCFunction)Noddy_name, METH_NOARGS,815 "Return the name, combining the first and last name"816 },817 {NULL} /* Страж */818};819820static PyTypeObject NoddyType = {821 PyVarObject_HEAD_INIT(NULL, 0)822 "noddy.Noddy", /* tp_name */823 sizeof(Noddy), /* tp_basicsize */824 0, /* tp_itemsize */825 (destructor)Noddy_dealloc, /* tp_dealloc */826 0, /* tp_print */827 0, /* tp_getattr */828 0, /* tp_setattr */829 0, /* tp_reserved */830 0, /* tp_repr */831 0, /* tp_as_number */832 0, /* tp_as_sequence */833 0, /* tp_as_mapping */834 0, /* tp_hash */835 0, /* tp_call */836 0, /* tp_str */837 0, /* tp_getattro */838 0, /* tp_setattro */839 0, /* tp_as_buffer */840 Py_TPFLAGS_DEFAULT |841 Py_TPFLAGS_BASETYPE, /* tp_flags */842 "Noddy objects", /* tp_doc */843 0, /* tp_traverse */844 0, /* tp_clear */845 0, /* tp_richcompare */846 0, /* tp_weaklistoffset */847 0, /* tp_iter */848 0, /* tp_iternext */849 Noddy_methods, /* tp_methods */850 Noddy_members, /* tp_members */851 Noddy_getseters, /* tp_getset */852 0, /* tp_base */853 0, /* tp_dict */854 0, /* tp_descr_get */855 0, /* tp_descr_set */856 0, /* tp_dictoffset */857 (initproc)Noddy_init, /* tp_init */858 0, /* tp_alloc */859 Noddy_new, /* tp_new */860};861862static PyModuleDef noddy3module = {863 PyModuleDef_HEAD_INIT,864 "noddy3",865 "Example module that creates an extension type.",866 -1,867 NULL, NULL, NULL, NULL, NULL868};869870PyMODINIT_FUNC871PyInit_noddy3(void)872{873 PyObject* m;874875 if (PyType_Ready(&NoddyType) < 0)876 return NULL;877878 m = PyModule_Create(&noddy3module);879 if (m == NULL)880 return NULL;881882 Py_INCREF(&NoddyType);883 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);884 return m;885}886```887888Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:889890```c891Noddy_getfirst(Noddy *self, void *closure)892{893 Py_INCREF(self->first);894 return self->first;895}896897static int898Noddy_setfirst(Noddy *self, PyObject *value, void *closure)899{900 if (value == NULL) {901 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");902 return -1;903 }904905 if (! PyUnicode_Check(value)) {906 PyErr_SetString(PyExc_TypeError,907 "The first attribute value must be a str");908 return -1;909 }910911 Py_DECREF(self->first);912 Py_INCREF(value);913 self->first = value;914915 return 0;916}917```918919Функции получения передаётся объект `Noddy` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это, например, можно использовать, чтобы позволить единому набору функций получения и установки решать, какой атрибут получать или устанавливать, на основе данных в замыкании.)920921Функции установки передаётся объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.922923Создаём массив структур `PyGetSetDef`:924925```c926static PyGetSetDef Noddy_getseters[] = {927 {"first",928 (getter)Noddy_getfirst, (setter)Noddy_setfirst,929 "first name",930 NULL},931 {"last",932 (getter)Noddy_getlast, (setter)Noddy_setlast,933 "last name",934 NULL},935 {NULL} /* Страж */936};937```938939и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_getset):940941```c942Noddy_getseters, /* tp_getset */943```944945для регистрации геттеров и сеттеров наших атрибутов.946947Последний элемент структуры `PyGetSetDef` – это упомянутое выше замыкание. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.948949Также удаляем определения членов для этих атрибутов:950951```c952static PyMemberDef Noddy_members[] = {953 {"number", T_INT, offsetof(Noddy, number), 0,954 "noddy number"},955 {NULL} /* Страж */956};957```958959Также необходимо обновить обработчик [`tp_init`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.4/extending/newtypes.html#id9):960961```c962static int963Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)964{965 PyObject *first=NULL, *last=NULL, *tmp;966967 static char *kwlist[] = {"first", "last", "number", NULL};968969 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,970 &first, &last,971 &self->number))972 return -1;973974 if (first) {975 tmp = self->first;976 Py_INCREF(first);977 self->first = first;978 Py_DECREF(tmp);979 }980981 if (last) {982 tmp = self->last;983 Py_INCREF(last);984 self->last = last;985 Py_DECREF(tmp);986 }987988 return 0;989}990```991992Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значения *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность того, что инициализация этих членов в конструкторе не удалась.993994Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как мы делали ранее, и добавляем дополнительное определение в файл `setup.py`.995996### 2.1.3. Поддержка циклической сборки мусора997998В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:9991000```c1001>>> l = []1002>>> l.append(l)1003>>> del l1004```10051006В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10071008Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.4/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:10091010```c1011>>> import noddy21012>>> n = noddy2.Noddy()1013>>> l = [n]1014>>> n.first = l1015```10161017Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типам необходимо заполнить два слота и установить флаг класса, включающий эти слоты:10181019```c1020#include <Python.h>1021#include "structmember.h"10221023typedef struct {1024 PyObject_HEAD1025 PyObject *first;1026 PyObject *last;1027 int number;1028} Noddy;10291030static int1031Noddy_traverse(Noddy *self, visitproc visit, void *arg)1032{1033 int vret;10341035 if (self->first) {1036 vret = visit(self->first, arg);1037 if (vret != 0)1038 return vret;1039 }1040 if (self->last) {1041 vret = visit(self->last, arg);1042 if (vret != 0)1043 return vret;1044 }10451046 return 0;1047}10481049static int1050Noddy_clear(Noddy *self)1051{1052 PyObject *tmp;10531054 tmp = self->first;1055 self->first = NULL;1056 Py_XDECREF(tmp);10571058 tmp = self->last;1059 self->last = NULL;1060 Py_XDECREF(tmp);10611062 return 0;1063}10641065static void1066Noddy_dealloc(Noddy* self)1067{1068 Noddy_clear(self);1069 Py_TYPE(self)->tp_free((PyObject*)self);1070}10711072static PyObject *1073Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1074{1075 Noddy *self;10761077 self = (Noddy *)type->tp_alloc(type, 0);1078 if (self != NULL) {1079 self->first = PyUnicode_FromString("");1080 if (self->first == NULL) {1081 Py_DECREF(self);1082 return NULL;1083 }10841085 self->last = PyUnicode_FromString("");1086 if (self->last == NULL) {1087 Py_DECREF(self);1088 return NULL;1089 }10901091 self->number = 0;1092 }10931094 return (PyObject *)self;1095}10961097static int1098Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1099{1100 PyObject *first=NULL, *last=NULL, *tmp;11011102 static char *kwlist[] = {"first", "last", "number", NULL};11031104 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,1105 &first, &last,1106 &self->number))1107 return -1;11081109 if (first) {1110 tmp = self->first;1111 Py_INCREF(first);1112 self->first = first;1113 Py_XDECREF(tmp);1114 }11151116 if (last) {1117 tmp = self->last;1118 Py_INCREF(last);1119 self->last = last;1120 Py_XDECREF(tmp);1121 }11221123 return 0;1124}11251126static PyMemberDef Noddy_members[] = {1127 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1128 "first name"},1129 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1130 "last name"},1131 {"number", T_INT, offsetof(Noddy, number), 0,1132 "noddy number"},1133 {NULL} /* Страж */1134};11351136static PyObject *1137Noddy_name(Noddy* self)1138{1139 if (self->first == NULL) {1140 PyErr_SetString(PyExc_AttributeError, "first");1141 return NULL;1142 }11431144 if (self->last == NULL) {1145 PyErr_SetString(PyExc_AttributeError, "last");1146 return NULL;1147 }11481149 return PyUnicode_FromFormat("%S %S", self->first, self->last);1150}11511152static PyMethodDef Noddy_methods[] = {1153 {"name", (PyCFunction)Noddy_name, METH_NOARGS,1154 "Return the name, combining the first and last name"1155 },1156 {NULL} /* Страж */1157};11581159static PyTypeObject NoddyType = {1160 PyVarObject_HEAD_INIT(NULL, 0)1161 "noddy.Noddy", /* tp_name */1162 sizeof(Noddy), /* tp_basicsize */1163 0, /* tp_itemsize */1164 (destructor)Noddy_dealloc, /* tp_dealloc */1165 0, /* tp_print */1166 0, /* tp_getattr */1167 0, /* tp_setattr */1168 0, /* tp_reserved */1169 0, /* tp_repr */1170 0, /* tp_as_number */1171 0, /* tp_as_sequence */1172 0, /* tp_as_mapping */1173 0, /* tp_hash */1174 0, /* tp_call */1175 0, /* tp_str */1176 0, /* tp_getattro */1177 0, /* tp_setattro */1178 0, /* tp_as_buffer */1179 Py_TPFLAGS_DEFAULT |1180 Py_TPFLAGS_BASETYPE |1181 Py_TPFLAGS_HAVE_GC, /* tp_flags */1182 "Noddy objects", /* tp_doc */1183 (traverseproc)Noddy_traverse, /* tp_traverse */1184 (inquiry)Noddy_clear, /* tp_clear */1185 0, /* tp_richcompare */1186 0, /* tp_weaklistoffset */1187 0, /* tp_iter */1188 0, /* tp_iternext */1189 Noddy_methods, /* tp_methods */1190 Noddy_members, /* tp_members */1191 0, /* tp_getset */1192 0, /* tp_base */1193 0, /* tp_dict */1194 0, /* tp_descr_get */1195 0, /* tp_descr_set */1196 0, /* tp_dictoffset */1197 (initproc)Noddy_init, /* tp_init */1198 0, /* tp_alloc */1199 Noddy_new, /* tp_new */1200};12011202static PyModuleDef noddy4module = {1203 PyModuleDef_HEAD_INIT,1204 "noddy4",1205 "Example module that creates an extension type.",1206 -1,1207 NULL, NULL, NULL, NULL, NULL1208};12091210PyMODINIT_FUNC1211PyInit_noddy4(void)1212{1213 PyObject* m;12141215 if (PyType_Ready(&NoddyType) < 0)1216 return NULL;12171218 m = PyModule_Create(&noddy4module);1219 if (m == NULL)1220 return NULL;12211222 Py_INCREF(&NoddyType);1223 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1224 return m;1225}1226```12271228Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:12291230```c1231static int1232Noddy_traverse(Noddy *self, visitproc visit, void *arg)1233{1234 int vret;12351236 if (self->first) {1237 vret = visit(self->first, arg);1238 if (vret != 0)1239 return vret;1240 }1241 if (self->last) {1242 vret = visit(self->last, arg);1243 if (vret != 0)1244 return vret;1245 }12461247 return 0;1248}1249```12501251Для каждого подобъекта, который может участвовать в циклах, необходимо вызвать функцию `visit()`, которая передаётся методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целочисленное значение, которое должно быть возвращено, если оно не равно нулю.12521253Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.4/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.4/c-api/gcsupport.html#c.Py_VISIT) функцию `Noddy_traverse()` можно упростить:12541255```c1256static int1257Noddy_traverse(Noddy *self, visitproc visit, void *arg)1258{1259 Py_VISIT(self->first);1260 Py_VISIT(self->last);1261 return 0;1262}1263```12641265> **Примечание**1266>1267> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.4/c-api/gcsupport.html#c.Py_VISIT). Это сделано для поощрения единообразия в этих однообразных реализациях.12681269Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах. Реализуем этот метод и перереализуем деаллокатор для его использования:12701271```c1272static int1273Noddy_clear(Noddy *self)1274{1275 PyObject *tmp;12761277 tmp = self->first;1278 self->first = NULL;1279 Py_XDECREF(tmp);12801281 tmp = self->last;1282 self->last = NULL;1283 Py_XDECREF(tmp);12841285 return 0;1286}12871288static void1289Noddy_dealloc(Noddy* self)1290{1291 Noddy_clear(self);1292 Py_TYPE(self)->tp_free((PyObject*)self);1293}1294```12951296Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы установить каждый член в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это, потому что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратный вызов в объект. Кроме того, поскольку теперь мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, который запускает сборку мусора. Если сборка мусора запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_traverse). Мы не можем рисковать тем, что `Noddy_traverse()` будет вызвана, когда счётчик ссылок члена упал до нуля, а его значение ещё не установлено в *NULL*.12971298Python предоставляет макрос [`Py_CLEAR()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_CLEAR), автоматизирующий аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_CLEAR) функцию `Noddy_clear()` можно упростить:12991300```c1301static int1302Noddy_clear(Noddy *self)1303{1304 Py_CLEAR(self->first);1305 Py_CLEAR(self->last);1306 return 0;1307}1308```13091310Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.4/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13111312```c1313Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1314```13151316В общем, это всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_free), их нужно было бы изменить для циклической сборки мусора. Большинство расширений будут использовать автоматически предоставленные версии.13171318### 2.1.4. Наследование от других типов13191320Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужную ему структуру `PyTypeObject`. Совместное использование этих структур `PyTypeObject` между модулями расширений может быть сложным.13211322В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.4/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.13231324```c1325>>> import shoddy1326>>> s = shoddy.Shoddy(range(3))1327>>> s.extend(s)1328>>> print(len(s))132961330>>> print(s.increment())133111332>>> print(s.increment())133321334```13351336```c1337#include <Python.h>13381339typedef struct {1340 PyListObject list;1341 int state;1342} Shoddy;13431344static PyObject *1345Shoddy_increment(Shoddy *self, PyObject *unused)1346{1347 self->state++;1348 return PyLong_FromLong(self->state);1349}13501351static PyMethodDef Shoddy_methods[] = {1352 {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1353 PyDoc_STR("increment state counter")},1354 {NULL, NULL},1355};13561357static int1358Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1359{1360 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1361 return -1;1362 self->state = 0;1363 return 0;1364}13651366static PyTypeObject ShoddyType = {1367 PyObject_HEAD_INIT(NULL)1368 "shoddy.Shoddy", /* tp_name */1369 sizeof(Shoddy), /* tp_basicsize */1370 0, /* tp_itemsize */1371 0, /* tp_dealloc */1372 0, /* tp_print */1373 0, /* tp_getattr */1374 0, /* tp_setattr */1375 0, /* tp_reserved */1376 0, /* tp_repr */1377 0, /* tp_as_number */1378 0, /* tp_as_sequence */1379 0, /* tp_as_mapping */1380 0, /* tp_hash */1381 0, /* tp_call */1382 0, /* tp_str */1383 0, /* tp_getattro */1384 0, /* tp_setattro */1385 0, /* tp_as_buffer */1386 Py_TPFLAGS_DEFAULT |1387 Py_TPFLAGS_BASETYPE, /* tp_flags */1388 0, /* tp_doc */1389 0, /* tp_traverse */1390 0, /* tp_clear */1391 0, /* tp_richcompare */1392 0, /* tp_weaklistoffset */1393 0, /* tp_iter */1394 0, /* tp_iternext */1395 Shoddy_methods, /* tp_methods */1396 0, /* tp_members */1397 0, /* tp_getset */1398 0, /* tp_base */1399 0, /* tp_dict */1400 0, /* tp_descr_get */1401 0, /* tp_descr_set */1402 0, /* tp_dictoffset */1403 (initproc)Shoddy_init, /* tp_init */1404 0, /* tp_alloc */1405 0, /* tp_new */1406};14071408static PyModuleDef shoddymodule = {1409 PyModuleDef_HEAD_INIT,1410 "shoddy",1411 "Shoddy module",1412 -1,1413 NULL, NULL, NULL, NULL, NULL1414};14151416PyMODINIT_FUNC1417PyInit_shoddy(void)1418{1419 PyObject *m;14201421 ShoddyType.tp_base = &PyList_Type;1422 if (PyType_Ready(&ShoddyType) < 0)1423 return NULL;14241425 m = PyModule_Create(&shoddymodule);1426 if (m == NULL)1427 return NULL;14281429 Py_INCREF(&ShoddyType);1430 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1431 return m;1432}1433```14341435Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.14361437```c1438typedef struct {1439 PyListObject list;1440 int state;1441} Shoddy;1442```14431444Основное различие для объектов производных типов заключается в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.14451446Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно приводить как к *PyListObject\**, так и к *Shoddy\**.14471448```c1449static int1450Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1451{1452 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1453 return -1;1454 self->state = 0;1455 return 0;1456}1457```14581459В методе `__init__` нашего типа видно, как вызвать метод `__init__` базового типа.14601461Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен непосредственно выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) – это будет сделано базовым классом при вызове его [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new).14621463При заполнении структуры [`PyTypeObject()`](https://python-all.ru/3.4/c-api/type.html#c.PyTypeObject) для типа `Shoddy` можно увидеть слот для `tp_base()`. Из-за проблем с кроссплатформенными компиляторами нельзя напрямую заполнять это поле значением [`PyList_Type()`](https://python-all.ru/3.4/c-api/list.html#c.PyList_Type); это можно сделать позже в функции `init()` модуля.14641465```c1466PyMODINIT_FUNC1467PyInit_shoddy(void)1468{1469 PyObject *m;14701471 ShoddyType.tp_base = &PyList_Type;1472 if (PyType_Ready(&ShoddyType) < 0)1473 return NULL;14741475 m = PyModule_Create(&shoddymodule);1476 if (m == NULL)1477 return NULL;14781479 Py_INCREF(&ShoddyType);1480 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1481 return m;1482}1483```14841485Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) в структуре типа должен быть заполнен слот [`tp_base`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_base). При создании нового типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.14861487После этого вызов [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль ничем не отличаются от базовых примеров с `Noddy`.14881489## 2.2. Методы типа14901491Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.14921493Ниже приведено определение [`PyTypeObject`](https://python-all.ru/3.4/c-api/type.html#c.PyTypeObject), в котором опущены некоторые поля, используемые только в отладочных сборках:14941495```c1496typedef struct _typeobject {1497 PyObject_VAR_HEAD1498 const char *tp_name; /* Для вывода в формате "<module>.<name>" */1499 Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */15001501 /* Методы для реализации стандартных операций */15021503 destructor tp_dealloc;1504 printfunc tp_print;1505 getattrfunc tp_getattr;1506 setattrfunc tp_setattr;1507 void *tp_reserved; /* ранее известный как tp_compare */1508 reprfunc tp_repr;15091510 /* Наборы методов для стандартных классов */15111512 PyNumberMethods *tp_as_number;1513 PySequenceMethods *tp_as_sequence;1514 PyMappingMethods *tp_as_mapping;15151516 /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15171518 hashfunc tp_hash;1519 ternaryfunc tp_call;1520 reprfunc tp_str;1521 getattrofunc tp_getattro;1522 setattrofunc tp_setattro;15231524 /* Функции для доступа к объекту как к буферу ввода/вывода */1525 PyBufferProcs *tp_as_buffer;15261527 /* Флаги для определения наличия опциональных/расширенных возможностей */1528 unsigned long tp_flags;15291530 const char *tp_doc; /* Строка документации */15311532 /* вызов функции для всех доступных объектов */1533 traverseproc tp_traverse;15341535 /* удаление ссылок на содержащиеся объекты */1536 inquiry tp_clear;15371538 /* расширенные сравнения */1539 richcmpfunc tp_richcompare;15401541 /* включение слабых ссылок */1542 Py_ssize_t tp_weaklistoffset;15431544 /* Итераторы */1545 getiterfunc tp_iter;1546 iternextfunc tp_iternext;15471548 /* Дескриптор атрибутов и механизмы подклассов */1549 struct PyMethodDef *tp_methods;1550 struct PyMemberDef *tp_members;1551 struct PyGetSetDef *tp_getset;1552 struct _typeobject *tp_base;1553 PyObject *tp_dict;1554 descrgetfunc tp_descr_get;1555 descrsetfunc tp_descr_set;1556 Py_ssize_t tp_dictoffset;1557 initproc tp_init;1558 allocfunc tp_alloc;1559 newfunc tp_new;1560 freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1561 inquiry tp_is_gc; /* Для PyObject_IS_GC */1562 PyObject *tp_bases;1563 PyObject *tp_mro; /* порядок разрешения методов */1564 PyObject *tp_cache;1565 PyObject *tp_subclasses;1566 PyObject *tp_weaklist;1567 destructor tp_del;15681569 /* Метка версии кэша атрибутов типа. Добавлено в версии 2.6. */1570 unsigned int tp_version_tag;15711572 destructor tp_finalize;15731574} PyTypeObject;1575```15761577Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.15781579Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.15801581```c1582const char *tp_name; /* Для вывода */1583```15841585Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!15861587```c1588Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */1589```15901591Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов данного типа. В Python есть встроенная поддержка структур переменной длины (например, строки, списки), для которой предназначено поле [`tp_itemsize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_itemsize). Об этом будет рассказано позже.15921593```c1594const char *tp_doc;1595```15961597Сюда можно поместить строку (или её адрес), которая должна возвращаться, когда скрипт на Python обращается к `obj.__doc__` для получения строки документации.15981599Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16001601### 2.2.1. Финализация и освобождение памяти16021603```c1604destructor tp_dealloc;1605```16061607Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа уменьшается до нуля и интерпретатор Python хочет освободить его. Если ваш тип требует освобождения памяти или другой очистки, этот код можно разместить здесь. Сам объект также должен быть освобождён здесь. Вот пример такой функции:16081609```c1610static void1611newdatatype_dealloc(newdatatypeobject * obj)1612{1613 free(obj->obj_UnderlyingDatatypePtr);1614 Py_TYPE(obj)->tp_free(obj);1615}1616```16171618Важное требование к функции деаллокатора – она не должна затрагивать ожидающие исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при нормальных возвратах), не предпринимается никаких действий для защиты деаллокаторов от того, что исключение уже установлено. Любые действия, выполняемые деаллокатором, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к ложным ошибкам интерпретатора. Правильный способ защиты – сохранить ожидающее исключение перед выполнением опасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/3.4/c-api/exceptions.html#c.PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/3.4/c-api/exceptions.html#c.PyErr_Restore):16191620```c1621static void1622my_dealloc(PyObject *obj)1623{1624 MyObject *self = (MyObject *) obj;1625 PyObject *cbresult;16261627 if (self->my_callback != NULL) {1628 PyObject *err_type, *err_value, *err_traceback;16291630 /* Сохраняет текущее состояние исключения */1631 PyErr_Fetch(&err_type, &err_value, &err_traceback);16321633 cbresult = PyObject_CallObject(self->my_callback, NULL);1634 if (cbresult == NULL)1635 PyErr_WriteUnraisable(self->my_callback);1636 else1637 Py_DECREF(cbresult);16381639 /* Восстанавливает сохранённое состояние исключения */1640 PyErr_Restore(err_type, err_value, err_traceback);16411642 Py_DECREF(self->my_callback);1643 }1644 Py_TYPE(obj)->tp_free((PyObject*)self);1645}1646```16471648> **Примечание**1649>1650> Существуют ограничения на то, что можно безопасно делать в функции деаллокатора. Во-первых, если ваш тип поддерживает сборку мусора (с помощью [`tp_traverse`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_traverse) и/или [`tp_clear`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_clear)), некоторые члены объекта могли быть очищены или финализированы к моменту вызова [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Во-вторых, в [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) объект находится в нестабильном состоянии: его счётчик ссылок равен нулю. Любой вызов нетривиального объекта или API (как в примере выше) может снова вызвать [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), что приведёт к двойному освобождению и краху.1651>1652> Начиная с Python 3.4, рекомендуется не помещать сложный код финализации в [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), а вместо этого использовать новый метод типа [`tp_finalize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_finalize).1653>1654> > **См. также**1655> >1656> > [**PEP 442**](https://python-all.ru/3.4/extending/newtypes.html) описывает новую схему финализации.16571658### 2.2.2. Представление объекта16591660В Python есть два способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.4/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str). (Функция [`print()`](https://python-all.ru/3.4/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str).) Оба обработчика необязательны.16611662```c1663reprfunc tp_repr;1664reprfunc tp_str;1665```16661667Обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызывается. Вот простой пример:16681669```c1670static PyObject *1671newdatatype_repr(newdatatypeobject * obj)1672{1673 return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1674 obj->obj_UnderlyingDatatypePtr->size);1675}1676```16771678Если обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr) не указан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_name) типа и уникальный идентификатор объекта.16791680Обработчик [`tp_str`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_str) для [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str) – это то же самое, что описанный выше обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr) для [`repr()`](https://python-all.ru/3.4/library/functions.html#repr); то есть он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str) на экземпляре вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr), но результирующая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_str) не указан, вместо него используется обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr).16811682Вот простой пример:16831684```c1685static PyObject *1686newdatatype_str(newdatatypeobject * obj)1687{1688 return PyUnicode_FromFormat("Stringified_newdatatype{{size:\%d}}",1689 obj->obj_UnderlyingDatatypePtr->size);1690}1691```16921693### 2.2.3. Управление атрибутами16941695Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.16961697Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, достаточно реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.16981699```c1700getattrfunc tp_getattr; /* char * version */1701setattrfunc tp_setattr;1702/* ... */1703getattrofunc tp_getattro; /* PyObject * version */1704setattrofunc tp_setattro;1705```17061707Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют обобщённые реализации, которые можно использовать для предоставления версии функций управления атрибутами с [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject). Фактическая потребность в специфичных для типа обработчиках атрибутов почти полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового обобщённого механизма.17081709#### 2.2.3.1. Общее управление атрибутами17101711Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:171217131. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready).17142. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17151716Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.17171718When [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) is called, it uses three tables referenced by the type object to create [*descriptor*](https://python-all.ru/3.4/glossary.html#term-descriptor)s which are placed in the dictionary of the type object. Each descriptor controls access to one attribute of the instance object. Each of the tables is optional; if all three are *NULL*, instances of the type will only have attributes that are inherited from their base type, and should leave the [`tp_getattro`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_getattro) and [`tp_setattro`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_setattro) fields *NULL* as well, allowing the base type to handle attributes.17191720Таблицы объявлены как три поля объекта типа:17211722```c1723struct PyMethodDef *tp_methods;1724struct PyMemberDef *tp_members;1725struct PyGetSetDef *tp_getset;1726```17271728Если [`tp_methods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_methods) не равен *NULL*, он должен указывать на массив структур [`PyMethodDef`](https://python-all.ru/3.4/c-api/structures.html#c.PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:17291730```c1731typedef struct PyMethodDef {1732 char *ml_name; /* имя метода */1733 PyCFunction ml_meth; /* функция реализации */1734 int ml_flags; /* флаги */1735 char *ml_doc; /* докстринг */1736} PyMethodDef;1737```17381739Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется ещё одна запись – это сигнальный элемент, обозначающий конец массива. Поле `ml_name` сигнального элемента должно быть равно *NULL*.17401741Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:17421743```c1744typedef struct PyMemberDef {1745 char *name;1746 int type;1747 int offset;1748 int flags;1749 char *doc;1750} PyMemberDef;1751```17521753Для каждой записи в таблице будет создан [*дескриптор*](https://python-all.ru/3.4/glossary.html#term-descriptor) и добавлен к типу; он сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.4/library/functions.html#type) должно содержать один из кодов типа, определённых в заголовочном файле `structmember.h`; это значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.17541755Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.17561757| Константа | Значение |1758| --- | --- |1759| `READONLY` | Никогда не доступен для записи. |1760| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1761| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1762| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |17631764Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.17651766Как и в таблице [`tp_methods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_methods), требуется сигнальный элемент со значением `name` равным *NULL*.17671768#### 2.2.3.2. Управление атрибутами, специфичное для типа17691770Для простоты здесь будет продемонстрирована только версия с `char*`; разница между версиями интерфейса с `char*` и [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject) заключается только в типе параметра name. Этот пример по сути делает то же самое, что и обобщённый пример выше, но не использует обобщённую поддержку, добавленную в Python 2.2. Он объясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, было понятно, что нужно делать.17711772Обработчик [`tp_getattr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, в которых вызывался бы метод [`__getattr__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__getattr__) класса.17731774Вот пример:17751776```c1777static PyObject *1778newdatatype_getattr(newdatatypeobject *obj, char *name)1779{1780 if (strcmp(name, "data") == 0)1781 {1782 return PyLong_FromLong(obj->data);1783 }17841785 PyErr_Format(PyExc_AttributeError,1786 "'%.50s' object has no attribute '%.400s'",1787 tp->tp_name, name);1788 return NULL;1789}1790```17911792Обработчик [`tp_setattr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_setattr) вызывается при вызове метода [`__setattr__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__delattr__) экземпляра класса. Когда атрибут должен быть удалён, третий параметр будет равен *NULL*. Вот пример, который просто возбуждает исключение; если бы это действительно было всё, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_setattr) должен быть установлен в *NULL*.17931794```c1795static int1796newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1797{1798 (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1799 return -1;1800}1801```18021803### 2.2.4. Сравнение объектов18041805```c1806richcmpfunc tp_richcompare;1807```18081809Обработчик [`tp_richcompare`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_richcompare) вызывается, когда требуются сравнения. Он аналогичен [*методам расширенного сравнения*](https://python-all.ru/3.4/reference/datamodel.html#richcmpfuncs), таким как [`__lt__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__lt__), и также вызывается функциями [`PyObject_RichCompare()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_RichCompare) и [`PyObject_RichCompareBool()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_RichCompareBool).18101811Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор – это один из `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GT`, `Py_LT` или `Py_GT`. Она должна сравнить два объекта в соответствии с указанным оператором и вернуть `Py_True` или `Py_False`, если сравнение успешно, `Py_NotImplemented`, чтобы указать, что сравнение не реализовано и следует попробовать метод сравнения другого объекта, или *NULL*, если было установлено исключение.18121813Вот пример реализации для типа данных, который считается равным, если размер внутреннего указателя одинаков:18141815```c1816static PyObject *1817newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)1818{1819 PyObject *result;1820 int c, size1, size2;18211822 /* код для проверки, что оба аргумента имеют нужный тип1823 newdatatype опущен */18241825 size1 = obj1->obj_UnderlyingDatatypePtr->size;1826 size2 = obj2->obj_UnderlyingDatatypePtr->size;18271828 switch (op) {1829 case Py_LT: c = size1 < size2; break;1830 case Py_LE: c = size1 <= size2; break;1831 case Py_EQ: c = size1 == size2; break;1832 case Py_NE: c = size1 != size2; break;1833 case Py_GT: c = size1 > size2; break;1834 case Py_GE: c = size1 >= size2; break;1835 }1836 result = c ? Py_True : Py_False;1837 Py_INCREF(result);1838 return result;1839 }1840```18411842### 2.2.5. Поддержка абстрактных протоколов18431844Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [*Уровень абстрактных объектов*](https://python-all.ru/3.4/c-api/abstract.html#abstract).18451846Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)18471848```c1849PyNumberMethods *tp_as_number;1850PySequenceMethods *tp_as_sequence;1851PyMappingMethods *tp_as_mapping;1852```18531854Если нужно, чтобы объект мог вести себя как число, последовательность или отображение, то следует поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyMappingMethods) соответственно. Заполнить эту структуру подходящими значениями нужно самостоятельно. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.18551856```c1857hashfunc tp_hash;1858```18591860Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:18611862```c1863static long1864newdatatype_hash(newdatatypeobject *obj)1865{1866 long result;1867 result = obj->obj_UnderlyingDatatypePtr->size;1868 result = result * 3;1869 return result;1870}1871```18721873```c1874ternaryfunc tp_call;1875```18761877Эта функция вызывается, когда экземпляр вашего типа данных «вызывается». Например, если `obj1` – экземпляр вашего типа данных, а скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_call).18781879Эта функция принимает три аргумента:188018811. *arg1* – это экземпляр типа данных, который является субъектом вызова. Если вызовом является `obj1('hello')`, то *arg1* – это `obj1`.18822. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/3.4/c-api/arg.html#c.PyArg_ParseTuple).18833. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/3.4/c-api/arg.html#c.PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите [`TypeError`](https://python-all.ru/3.4/library/exceptions.html#TypeError) с сообщением о том, что именованные аргументы не поддерживаются.18841885Вот бессистемный пример реализации функции call.18861887```c1888/* Реализовать функцию вызова.1889 * obj1 – экземпляр, принимающий вызов.1890 * obj2 – кортеж, содержащий аргументы вызова, в данном1891 * случае 3 строки.1892 */1893static PyObject *1894newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1895{1896 PyObject *result;1897 char *arg1;1898 char *arg2;1899 char *arg3;19001901 if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1902 return NULL;1903 }1904 result = PyUnicode_FromFormat(1905 "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1906 obj->obj_UnderlyingDatatypePtr->size,1907 arg1, arg2, arg3);1908 return result;1909}1910```19111912```c1913/* Итераторы */1914getiterfunc tp_iter;1915iternextfunc tp_iternext;1916```19171918Эти функции обеспечивают поддержку протокола итератора. Любой объект, который должен поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика: `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.19191920Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), следует создавать и возвращать новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, а также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.19211922Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только один раз). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.4/library/exceptions.html#StopIteration); пропуск исключения может дать несколько лучшую производительность. Если произошла реальная ошибка, он должен установить исключение и вернуть *NULL*.19231924### 2.2.6. Поддержка слабых ссылок19251926Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).19271928Чтобы объект можно было использовать в слабых ссылках, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_weaklistoffset) соответствующего объекта типа в смещение этого поля. Например, тип экземпляра определён следующей структурой:19291930```c1931typedef struct {1932 PyObject_HEAD1933 PyClassObject *in_class; /* Объект класса */1934 PyObject *in_dict; /* Словарь */1935 PyObject *in_weakreflist; /* Список слабых ссылок */1936} PyInstanceObject;1937```19381939Статически объявленный объект типа для экземпляров определяется следующим образом:19401941```c1942PyTypeObject PyInstance_Type = {1943 PyVarObject_HEAD_INIT(&PyType_Type, 0)1944 0,1945 "module.instance",19461947 /* Многое опущено для краткости... */19481949 Py_TPFLAGS_DEFAULT, /* tp_flags */1950 0, /* tp_doc */1951 0, /* tp_traverse */1952 0, /* tp_clear */1953 0, /* tp_richcompare */1954 offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */1955};1956```19571958Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:19591960```c1961static PyObject *1962instance_new() {1963 /* Прочие детали инициализации опущены для краткости */19641965 self->in_weakreflist = NULL;19661967 return (PyObject *) self;1968}1969```19701971Единственное дополнение заключается в том, что деструктор должен вызывать менеджер слабых ссылок для очистки любых слабых ссылок. Это требуется только в том случае, если список слабых ссылок не равен *NULL*:19721973```c1974static void1975instance_dealloc(PyInstanceObject *inst)1976{1977 /* Выделить временные объекты, если нужно, но не начинать1978 уничтожение пока не начинать1979 */19801981 if (inst->in_weakreflist != NULL)1982 PyObject_ClearWeakRefs((PyObject *) inst);19831984 /* Продолжить уничтожение объекта обычным образом. */1985}1986```19871988### 2.2.7. Дополнительные рекомендации19891990Помните, что большинство этих функций можно опустить; в этом случае нужно указать `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с дистрибутивом исходного кода Python.19911992Чтобы узнать, как реализовать какой-либо конкретный метод для вашего нового типа данных, сделайте следующее: скачайте и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем найдите в файлах C `tp_` плюс нужную функцию (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.19931994Когда нужно проверить, что объект является экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_TypeCheck). Пример её использования может выглядеть следующим образом:19951996```c1997if (! PyObject_TypeCheck(some_object, &MyType)) {1998 PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");1999 return NULL;2000}2001```20022003Сноски20042005| [\[1\]](https://python-all.ru/3.4/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2006| --- | --- |20072008| [\[2\]](https://python-all.ru/3.4/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы, которые позволяют «открепить» объект от сборки мусора, однако эти вызовы являются продвинутыми и не рассматриваются здесь. |2009| --- | --- |20102011| [\[3\]](https://python-all.ru/3.4/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2012| --- | --- |20132014| [\[4\]](https://python-all.ru/3.4/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2015| --- | --- |2016