newtypes.md
1> **Источник:** https://python-all.ru/2.7/extending/newtypes.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 2. Определение новых типов89Как упоминалось в предыдущей главе, Python позволяет разработчику модуля расширения определять новые типы, которыми можно манипулировать из кода Python, подобно строкам и спискам в ядре Python.1011Это несложно; код всех типов расширений следует определённому шаблону, но есть несколько деталей, которые необходимо понять, прежде чем приступать к работе.1213> **Примечание**14>15> Способ определения новых типов кардинально (и к лучшему) изменился в Python 2.2. В этом документе описано, как определять новые типы для Python 2.2 и новее. Если вам нужно поддерживать более старые версии Python, придётся обратиться к [более старым версиям этой документации](https://python-all.ru/2.7/extending/newtypes.html).1617## 2.1. Основы1819Среда выполнения Python рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject). [`PyObject`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject) – не очень сложный объект: он содержит только счётчик ссылок и указатель на «объект типа» объекта. В этом и заключается суть: объект типа определяет, какие функции (на C) будут вызваны, когда, например, у объекта запрашивается атрибут или он умножается на другой объект. Эти функции на C называются «методами типа».2021Итак, чтобы определить новый тип объекта, необходимо создать новый объект типа.2223Такие вещи лучше всего объяснять на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип:2425```c26#include <Python.h>2728typedef struct {29 PyObject_HEAD30 /* Сюда помещаются поля, специфичные для типа. */31} noddy_NoddyObject;3233static PyTypeObject noddy_NoddyType = {34 PyVarObject_HEAD_INIT(NULL, 0)35 "noddy.Noddy", /* tp_name */36 sizeof(noddy_NoddyObject), /* tp_basicsize */37 0, /* tp_itemsize */38 0, /* tp_dealloc */39 0, /* tp_print */40 0, /* tp_getattr */41 0, /* tp_setattr */42 0, /* tp_compare */43 0, /* tp_repr */44 0, /* tp_as_number */45 0, /* tp_as_sequence */46 0, /* tp_as_mapping */47 0, /* tp_hash */48 0, /* tp_call */49 0, /* tp_str */50 0, /* tp_getattro */51 0, /* tp_setattro */52 0, /* tp_as_buffer */53 Py_TPFLAGS_DEFAULT, /* tp_flags */54 "Noddy objects", /* tp_doc */55};5657static PyMethodDef noddy_methods[] = {58 {NULL} /* Страж */59};6061#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */62#define PyMODINIT_FUNC void63#endif64PyMODINIT_FUNC65initnoddy(void) 66{67 PyObject* m;6869 noddy_NoddyType.tp_new = PyType_GenericNew;70 if (PyType_Ready(&noddy_NoddyType) < 0)71 return;7273 m = Py_InitModule3("noddy", noddy_methods,74 "Example module that creates an extension type.");7576 Py_INCREF(&noddy_NoddyType);77 PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);78}79```8081Сразу это может показаться большим объёмом, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы.8283Первое, что будет новым:8485```c86typedef struct {87 PyObject_HEAD88} noddy_NoddyObject;89```9091Вот что будет содержать объект Noddy – в данном случае не более того, что содержит любой объект Python: счётчик ссылок и указатель на объект типа. Это поля, которые приносит макрос `PyObject_HEAD`. Причина существования макроса – стандартизировать структуру и добавить специальные поля отладки в отладочных сборках. Обратите внимание, что после макроса `PyObject_HEAD` точка с запятой не ставится; она уже включена в определение макроса. Будьте осторожны, случайно её не добавить – по привычке это легко сделать, и ваш компилятор, возможно, не пожалуется, но чей-нибудь другой – почти наверняка! (В Windows MSVC, как известно, считает это ошибкой и отказывается компилировать код.)9293Для сравнения давайте взглянем на соответствующее определение для стандартных целых чисел Python:9495```c96typedef struct {97 PyObject_HEAD98 long ob_ival;99} PyIntObject;100```101102Далее переходим к самому главному – объекту типа.103104```c105static PyTypeObject noddy_NoddyType = {106 PyVarObject_HEAD_INIT(NULL, 0)107 "noddy.Noddy", /* tp_name */108 sizeof(noddy_NoddyObject), /* tp_basicsize */109 0, /* tp_itemsize */110 0, /* tp_dealloc */111 0, /* tp_print */112 0, /* tp_getattr */113 0, /* tp_setattr */114 0, /* tp_compare */115 0, /* tp_repr */116 0, /* tp_as_number */117 0, /* tp_as_sequence */118 0, /* tp_as_mapping */119 0, /* tp_hash */120 0, /* tp_call */121 0, /* tp_str */122 0, /* tp_getattro */123 0, /* tp_setattro */124 0, /* tp_as_buffer */125 Py_TPFLAGS_DEFAULT, /* tp_flags */126 "Noddy objects", /* tp_doc */127};128```129130Если теперь заглянуть в определение [`PyTypeObject`](https://python-all.ru/2.7/c-api/type.html#c.PyTypeObject) в `object.h`, можно увидеть, что у него гораздо больше полей, чем в приведённом выше определении. Остальные поля будут заполнены нулями компилятором C, и общепринятой практикой является не указывать их явно, если они не нужны.131132Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:133134```c135PyVarObject_HEAD_INIT(NULL, 0)136```137138Эта строка – небольшой изъян; хотелось бы написать:139140```c141PyVarObject_HEAD_INIT(&PyType_Type, 0)142```143144поскольку типом объекта типа является «type», но это не строго соответствует C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready).145146```c147"noddy.Noddy", /* tp_name */148```149150Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:151152```c153>>> "" + noddy.new_noddy()154Traceback (most recent call last):155 File "<stdin>", line 1, in <module>156TypeError: cannot add type "noddy.Noddy" to string157```158159Обратите внимание, что имя – это составное имя, включающее как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы задаём имя типа как `noddy.Noddy`. Один из побочных эффектов использования имени без точки состоит в том, что инструмент документации pydoc не будет отображать новый тип в документации модуля.160161```c162sizeof(noddy_NoddyObject), /* tp_basicsize */163```164165Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New()`](https://python-all.ru/2.7/c-api/allocation.html#c.PyObject_New).166167> **Примечание**168>169> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/2.7/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для [`tp_basicsize`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/2.7/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.170171```c1720, /* tp_itemsize */173```174175Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.176177Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/2.7/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).178179```c180Py_TPFLAGS_DEFAULT, /* tp_flags */181```182183Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые текущей версией Python.184185Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_doc).186187```c188"Noddy objects", /* tp_doc */189```190191Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.192193Пока всё, что нам нужно уметь делать, – создавать новые объекты `Noddy`. Чтобы включить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new). В данном случае можно просто использовать реализацию по умолчанию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_GenericNew). Хотелось бы просто присвоить это слоту [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new), но из соображений переносимости мы не можем статически инициализировать член структуры функцией, определённой в другом модуле C, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready):194195```c196noddy_NoddyType.tp_new = PyType_GenericNew;197if (PyType_Ready(&noddy_NoddyType) < 0)198 return;199```200201Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!202203Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `initnoddy()`:204205```c206if (PyType_Ready(&noddy_NoddyType) < 0)207 return;208```209210Это инициализирует тип `Noddy`, заполняя ряд членов, включая `ob_type`, который мы изначально установили в *NULL*.211212```c213PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);214```215216Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy` вызовом класса `Noddy`:217218```c219>>> import noddy220>>> mynoddy = noddy.Noddy()221```222223Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `noddy.c` и224225```c226from distutils.core import setup, Extension227setup(name="noddy", version="1.0",228 ext_modules=[Extension("noddy", ["noddy.c"])])229```230231в файл с именем `setup.py`; затем введите232233```console234$ python setup.py build235```236237в командной оболочке должен создать файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поэкспериментировать с объектами Noddy.238239Было не так сложно, правда?240241Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.242243### 2.1.1. Добавление данных и методов к базовому примеру244245Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `noddy2`, который добавляет эти возможности:246247```c248#include <Python.h>249#include "structmember.h"250251typedef struct {252 PyObject_HEAD253 PyObject *first; /* имя */254 PyObject *last; /* фамилия */255 int number;256} Noddy;257258static void259Noddy_dealloc(Noddy* self)260{261 Py_XDECREF(self->first);262 Py_XDECREF(self->last);263 Py_TYPE(self)->tp_free((PyObject*)self);264}265266static PyObject *267Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)268{269 Noddy *self;270271 self = (Noddy *)type->tp_alloc(type, 0);272 if (self != NULL) {273 self->first = PyString_FromString("");274 if (self->first == NULL) {275 Py_DECREF(self);276 return NULL;277 }278279 self->last = PyString_FromString("");280 if (self->last == NULL) {281 Py_DECREF(self);282 return NULL;283 }284285 self->number = 0;286 }287288 return (PyObject *)self;289}290291static int292Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)293{294 PyObject *first=NULL, *last=NULL, *tmp;295296 static char *kwlist[] = {"first", "last", "number", NULL};297298 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,299 &first, &last,300 &self->number))301 return -1;302303 if (first) {304 tmp = self->first;305 Py_INCREF(first);306 self->first = first;307 Py_XDECREF(tmp);308 }309310 if (last) {311 tmp = self->last;312 Py_INCREF(last);313 self->last = last;314 Py_XDECREF(tmp);315 }316317 return 0;318}319320static PyMemberDef Noddy_members[] = {321 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,322 "first name"},323 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,324 "last name"},325 {"number", T_INT, offsetof(Noddy, number), 0,326 "noddy number"},327 {NULL} /* Страж */328};329330static PyObject *331Noddy_name(Noddy* self)332{333 static PyObject *format = NULL;334 PyObject *args, *result;335336 if (format == NULL) {337 format = PyString_FromString("%s %s");338 if (format == NULL)339 return NULL;340 }341342 if (self->first == NULL) {343 PyErr_SetString(PyExc_AttributeError, "first");344 return NULL;345 }346347 if (self->last == NULL) {348 PyErr_SetString(PyExc_AttributeError, "last");349 return NULL;350 }351352 args = Py_BuildValue("OO", self->first, self->last);353 if (args == NULL)354 return NULL;355356 result = PyString_Format(format, args);357 Py_DECREF(args);358359 return result;360}361362static PyMethodDef Noddy_methods[] = {363 {"name", (PyCFunction)Noddy_name, METH_NOARGS,364 "Return the name, combining the first and last name"365 },366 {NULL} /* Страж */367};368369static PyTypeObject NoddyType = {370 PyVarObject_HEAD_INIT(NULL, 0)371 "noddy.Noddy", /* tp_name */372 sizeof(Noddy), /* tp_basicsize */373 0, /* tp_itemsize */374 (destructor)Noddy_dealloc, /* tp_dealloc */375 0, /* tp_print */376 0, /* tp_getattr */377 0, /* tp_setattr */378 0, /* tp_compare */379 0, /* tp_repr */380 0, /* tp_as_number */381 0, /* tp_as_sequence */382 0, /* tp_as_mapping */383 0, /* tp_hash */384 0, /* tp_call */385 0, /* tp_str */386 0, /* tp_getattro */387 0, /* tp_setattro */388 0, /* tp_as_buffer */389 Py_TPFLAGS_DEFAULT |390 Py_TPFLAGS_BASETYPE, /* tp_flags */391 "Noddy objects", /* tp_doc */392 0, /* tp_traverse */393 0, /* tp_clear */394 0, /* tp_richcompare */395 0, /* tp_weaklistoffset */396 0, /* tp_iter */397 0, /* tp_iternext */398 Noddy_methods, /* tp_methods */399 Noddy_members, /* tp_members */400 0, /* tp_getset */401 0, /* tp_base */402 0, /* tp_dict */403 0, /* tp_descr_get */404 0, /* tp_descr_set */405 0, /* tp_dictoffset */406 (initproc)Noddy_init, /* tp_init */407 0, /* tp_alloc */408 Noddy_new, /* tp_new */409};410411static PyMethodDef module_methods[] = {412 {NULL} /* Страж */413};414415#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */416#define PyMODINIT_FUNC void417#endif418PyMODINIT_FUNC419initnoddy2(void)420{421 PyObject* m;422423 if (PyType_Ready(&NoddyType) < 0)424 return;425426 m = Py_InitModule3("noddy2", module_methods,427 "Example module that creates an extension type.");428429 if (m == NULL)430 return;431432 Py_INCREF(&NoddyType);433 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);434}435```436437Эта версия модуля содержит ряд изменений.438439Мы добавили дополнительный include:440441```c442#include <structmember.h>443```444445Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.446447Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа было сокращено до `NoddyType`.448449Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – целое число.450451Структура объекта обновляется соответствующим образом:452453```c454typedef struct {455 PyObject_HEAD456 PyObject *first;457 PyObject *last;458 int number;459} Noddy;460```461462Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:463464```c465static void466Noddy_dealloc(Noddy* self)467{468 Py_XDECREF(self->first);469 Py_XDECREF(self->last);470 Py_TYPE(self)->tp_free((PyObject*)self);471}472```473474который присваивается члену [`tp_dealloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):475476```c477(destructor)Noddy_dealloc, /*tp_dealloc*/478```479480Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/2.7/c-api/refcounting.html#c.Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, поскольку объект может быть экземпляром подкласса.481482Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:483484```c485static PyObject *486Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)487{488 Noddy *self;489490 self = (Noddy *)type->tp_alloc(type, 0);491 if (self != NULL) {492 self->first = PyString_FromString("");493 if (self->first == NULL)494 {495 Py_DECREF(self);496 return NULL;497 }498499 self->last = PyString_FromString("");500 if (self->last == NULL)501 {502 Py_DECREF(self);503 return NULL;504 }505506 self->number = 0;507 }508509 return (PyObject *)self;510}511```512513и устанавливаем его в член [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new):514515```c516Noddy_new, /* tp_new */517```518519Новый метод отвечает за создание (в отличие от инициализации) объектов типа. Он доступен в Python как метод [`__new__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__new__). См. статью «Unifying types and classes in Python» для подробного обсуждения метода [`__new__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__new__). Одна из причин реализовать новый метод – гарантировать начальные значения переменных экземпляра. В данном случае мы используем новый метод, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_GenericNew) в качестве нового метода, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.520521Метод new – это статический метод, которому передаются создаваемый тип и любые аргументы, переданные при вызове типа, и который возвращает созданный новый объект. Методы new всегда принимают позиционные и именованные аргументы, но часто игнорируют их, оставляя обработку аргументов методам инициализации. Обратите внимание: если тип поддерживает наследование, переданный тип может не быть определяемым типом. Метод new вызывает слот tp\_alloc для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready) заполняет его за нас, наследуя от нашего базового класса, который по умолчанию является [`object`](https://python-all.ru/2.7/library/functions.html#object). Большинство типов используют выделение по умолчанию.522523> **Примечание**524>525> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new) или [`__new__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__new__) базового типа), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов (MRO) во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы можете не создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.TypeError).)526527Мы предоставляем функцию инициализации:528529```c530static int531Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)532{533 PyObject *first=NULL, *last=NULL, *tmp;534535 static char *kwlist[] = {"first", "last", "number", NULL};536537 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,538 &first, &last,539 &self->number))540 return -1;541542 if (first) {543 tmp = self->first;544 Py_INCREF(first);545 self->first = first;546 Py_XDECREF(tmp);547 }548549 if (last) {550 tmp = self->last;551 Py_INCREF(last);552 self->last = last;553 Py_XDECREF(tmp);554 }555556 return 0;557}558```559560заполняя слот [`tp_init`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_init).561562```c563(initproc)Noddy_init, /* tp_init */564```565566Слот [`tp_init`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, мы не можем гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при десериализации (unpickling) объектов и может быть переопределён. Наш инициализатор принимает аргументы для задания начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы.567568Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__init__) наших объектов. По этой причине нужно быть особенно осторожным при присваивании новых значений. Может возникнуть искушение, например, присвоить члену `first` значение следующим образом:569570```c571if (first) {572 Py_XDECREF(self->first);573 Py_INCREF(first);574 self->first = first;575}576```577578Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому им может быть любой объект. У него может быть деструктор, вызывающий выполнение кода, который пытается получить доступ к члену `first`. Чтобы быть параноиками и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?579580- когда точно известно, что счётчик ссылок больше 1581- когда известно, что освобождение объекта [1](https://python-all.ru/2.7/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа582- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), когда сборка мусора не поддерживается [2](https://python-all.ru/2.7/extending/newtypes.html#id8)583584Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:585586```c587static PyMemberDef Noddy_members[] = {588 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,589 "first name"},590 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,591 "last name"},592 {"number", T_INT, offsetof(Noddy, number), 0,593 "noddy number"},594 {NULL} /* Страж */595};596```597598и поместить определения в слот [`tp_members`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_members):599600```c601Noddy_members, /* tp_members */602```603604Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Управление атрибутами общего назначения](https://python-all.ru/2.7/extending/newtypes.html#generic-attribute-management) ниже.605606Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.607608Мы определяем один метод `name()`, который выводит имя объекта как конкатенацию имени и фамилии.609610```c611static PyObject *612Noddy_name(Noddy* self)613{614 static PyObject *format = NULL;615 PyObject *args, *result;616617 if (format == NULL) {618 format = PyString_FromString("%s %s");619 if (format == NULL)620 return NULL;621 }622623 if (self->first == NULL) {624 PyErr_SetString(PyExc_AttributeError, "first");625 return NULL;626 }627628 if (self->last == NULL) {629 PyErr_SetString(PyExc_AttributeError, "last");630 return NULL;631 }632633 args = Py_BuildValue("OO", self->first, self->last);634 if (args == NULL)635 return NULL;636637 result = PyString_Format(format, args);638 Py_DECREF(args);639640 return result;641}642```643644Метод реализован как функция на C, которая принимает экземпляр `Noddy` (или подкласс `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:645646```c647def name(self):648 return "%s %s" % (self.first, self.last)649```650651Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать в следующем разделе.652653Теперь, когда мы определили метод, нужно создать массив определений методов:654655```c656static PyMethodDef Noddy_methods[] = {657 {"name", (PyCFunction)Noddy_name, METH_NOARGS,658 "Return the name, combining the first and last name"659 },660 {NULL} /* Страж */661};662```663664и присваиваем их слоту [`tp_methods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_methods):665666```c667Noddy_methods, /* tp_methods */668```669670Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/2.7/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаётся аргументов.671672Наконец, мы сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы аккуратно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/2.7/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флага класса:673674```c675Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/676```677678Мы переименовываем `initnoddy()` в `initnoddy2()` и обновляем имя модуля, передаваемое в [`Py_InitModule3()`](https://python-all.ru/2.7/c-api/allocation.html#c.Py_InitModule3).679680Наконец, мы обновляем наш файл `setup.py`, чтобы собрать новый модуль:681682```c683from distutils.core import setup, Extension684setup(name="noddy", version="1.0",685 ext_modules=[686 Extension("noddy", ["noddy.c"]),687 Extension("noddy2", ["noddy2.c"]),688 ])689```690691### 2.1.2. Предоставление более точного управления атрибутами данных692693В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.694695```c696#include <Python.h>697#include "structmember.h"698699typedef struct {700 PyObject_HEAD701 PyObject *first;702 PyObject *last;703 int number;704} Noddy;705706static void707Noddy_dealloc(Noddy* self)708{709 Py_XDECREF(self->first);710 Py_XDECREF(self->last);711 Py_TYPE(self)->tp_free((PyObject*)self);712}713714static PyObject *715Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)716{717 Noddy *self;718719 self = (Noddy *)type->tp_alloc(type, 0);720 if (self != NULL) {721 self->first = PyString_FromString("");722 if (self->first == NULL) {723 Py_DECREF(self);724 return NULL;725 }726727 self->last = PyString_FromString("");728 if (self->last == NULL) {729 Py_DECREF(self);730 return NULL;731 }732733 self->number = 0;734 }735736 return (PyObject *)self;737}738739static int740Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)741{742 PyObject *first=NULL, *last=NULL, *tmp;743744 static char *kwlist[] = {"first", "last", "number", NULL};745746 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,747 &first, &last,748 &self->number))749 return -1;750751 if (first) {752 tmp = self->first;753 Py_INCREF(first);754 self->first = first;755 Py_DECREF(tmp);756 }757758 if (last) {759 tmp = self->last;760 Py_INCREF(last);761 self->last = last;762 Py_DECREF(tmp);763 }764765 return 0;766}767768static PyMemberDef Noddy_members[] = {769 {"number", T_INT, offsetof(Noddy, number), 0,770 "noddy number"},771 {NULL} /* Страж */772};773774static PyObject *775Noddy_getfirst(Noddy *self, void *closure)776{777 Py_INCREF(self->first);778 return self->first;779}780781static int782Noddy_setfirst(Noddy *self, PyObject *value, void *closure)783{784 if (value == NULL) {785 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");786 return -1;787 }788789 if (! PyString_Check(value)) {790 PyErr_SetString(PyExc_TypeError,791 "The first attribute value must be a string");792 return -1;793 }794795 Py_DECREF(self->first);796 Py_INCREF(value);797 self->first = value;798799 return 0;800}801802static PyObject *803Noddy_getlast(Noddy *self, void *closure)804{805 Py_INCREF(self->last);806 return self->last;807}808809static int810Noddy_setlast(Noddy *self, PyObject *value, void *closure)811{812 if (value == NULL) {813 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");814 return -1;815 }816817 if (! PyString_Check(value)) {818 PyErr_SetString(PyExc_TypeError,819 "The last attribute value must be a string");820 return -1;821 }822823 Py_DECREF(self->last);824 Py_INCREF(value);825 self->last = value;826827 return 0;828}829830static PyGetSetDef Noddy_getseters[] = {831 {"first",832 (getter)Noddy_getfirst, (setter)Noddy_setfirst,833 "first name",834 NULL},835 {"last",836 (getter)Noddy_getlast, (setter)Noddy_setlast,837 "last name",838 NULL},839 {NULL} /* Страж */840};841842static PyObject *843Noddy_name(Noddy* self)844{845 static PyObject *format = NULL;846 PyObject *args, *result;847848 if (format == NULL) {849 format = PyString_FromString("%s %s");850 if (format == NULL)851 return NULL;852 }853854 args = Py_BuildValue("OO", self->first, self->last);855 if (args == NULL)856 return NULL;857858 result = PyString_Format(format, args);859 Py_DECREF(args);860861 return result;862}863864static PyMethodDef Noddy_methods[] = {865 {"name", (PyCFunction)Noddy_name, METH_NOARGS,866 "Return the name, combining the first and last name"867 },868 {NULL} /* Страж */869};870871static PyTypeObject NoddyType = {872 PyVarObject_HEAD_INIT(NULL, 0)873 "noddy.Noddy", /* tp_name */874 sizeof(Noddy), /* tp_basicsize */875 0, /* tp_itemsize */876 (destructor)Noddy_dealloc, /* tp_dealloc */877 0, /* tp_print */878 0, /* tp_getattr */879 0, /* tp_setattr */880 0, /* tp_compare */881 0, /* tp_repr */882 0, /* tp_as_number */883 0, /* tp_as_sequence */884 0, /* tp_as_mapping */885 0, /* tp_hash */886 0, /* tp_call */887 0, /* tp_str */888 0, /* tp_getattro */889 0, /* tp_setattro */890 0, /* tp_as_buffer */891 Py_TPFLAGS_DEFAULT |892 Py_TPFLAGS_BASETYPE, /* tp_flags */893 "Noddy objects", /* tp_doc */894 0, /* tp_traverse */895 0, /* tp_clear */896 0, /* tp_richcompare */897 0, /* tp_weaklistoffset */898 0, /* tp_iter */899 0, /* tp_iternext */900 Noddy_methods, /* tp_methods */901 Noddy_members, /* tp_members */902 Noddy_getseters, /* tp_getset */903 0, /* tp_base */904 0, /* tp_dict */905 0, /* tp_descr_get */906 0, /* tp_descr_set */907 0, /* tp_dictoffset */908 (initproc)Noddy_init, /* tp_init */909 0, /* tp_alloc */910 Noddy_new, /* tp_new */911};912913static PyMethodDef module_methods[] = {914 {NULL} /* Страж */915};916917#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */918#define PyMODINIT_FUNC void919#endif920PyMODINIT_FUNC921initnoddy3(void)922{923 PyObject* m;924925 if (PyType_Ready(&NoddyType) < 0)926 return;927928 m = Py_InitModule3("noddy3", module_methods,929 "Example module that creates an extension type.");930931 if (m == NULL)932 return;933934 Py_INCREF(&NoddyType);935 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);936}937```938939Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:940941```c942Noddy_getfirst(Noddy *self, void *closure)943{944 Py_INCREF(self->first);945 return self->first;946}947948static int949Noddy_setfirst(Noddy *self, PyObject *value, void *closure)950{951 if (value == NULL) {952 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");953 return -1;954 }955956 if (! PyString_Check(value)) {957 PyErr_SetString(PyExc_TypeError,958 "The first attribute value must be a string");959 return -1;960 }961962 Py_DECREF(self->first);963 Py_INCREF(value);964 self->first = value;965966 return 0;967}968```969970Функция-getter получает объект `Noddy` и «замыкание» (closure), которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает продвинутое использование, при котором данные определения передаются getter'у и setter'у. Это может, например, использоваться, чтобы позволить одному набору функций getter и setter определять, какой атрибут получать или устанавливать, на основе данных в замыкании.)971972Функция-setter получает объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, и в этом случае атрибут удаляется. В нашем setter'е мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.973974Мы создаём массив структур [`PyGetSetDef`](https://python-all.ru/2.7/c-api/structures.html#c.PyGetSetDef):975976```c977static PyGetSetDef Noddy_getseters[] = {978 {"first",979 (getter)Noddy_getfirst, (setter)Noddy_setfirst,980 "first name",981 NULL},982 {"last",983 (getter)Noddy_getlast, (setter)Noddy_setlast,984 "last name",985 NULL},986 {NULL} /* Страж */987};988```989990и регистрируем его в слоте [`tp_getset`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_getset):991992```c993Noddy_getseters, /* tp_getset */994```995996для регистрации геттеров и сеттеров наших атрибутов.997998Последний элемент в структуре [`PyGetSetDef`](https://python-all.ru/2.7/c-api/structures.html#c.PyGetSetDef) – это замыкание, упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.9991000Также удаляем определения членов для этих атрибутов:10011002```c1003static PyMemberDef Noddy_members[] = {1004 {"number", T_INT, offsetof(Noddy, number), 0,1005 "noddy number"},1006 {NULL} /* Страж */1007};1008```10091010Нам также нужно обновить обработчик [`tp_init`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передавать только строки [3](https://python-all.ru/2.7/extending/newtypes.html#id9):10111012```c1013static int1014Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1015{1016 PyObject *first=NULL, *last=NULL, *tmp;10171018 static char *kwlist[] = {"first", "last", "number", NULL};10191020 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,1021 &first, &last,1022 &self->number))1023 return -1;10241025 if (first) {1026 tmp = self->first;1027 Py_INCREF(first);1028 self->first = first;1029 Py_DECREF(tmp);1030 }10311032 if (last) {1033 tmp = self->last;1034 Py_INCREF(last);1035 self->last = last;1036 Py_DECREF(tmp);1037 }10381039 return 0;1040}1041```10421043Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значение *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/2.7/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/2.7/c-api/refcounting.html#c.Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность, что инициализация этих членов завершилась неудачей в конструкторе.10441045Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.10461047### 2.1.3. Поддержка циклической сборки мусора10481049В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:10501051```c1052>>> l = []1053>>> l.append(l)1054>>> del l1055```10561057В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10581059Во второй версии примера `Noddy` мы разрешили хранить в атрибутах `first` или `last` объекты любого типа [4](https://python-all.ru/2.7/extending/newtypes.html#id10). Это означает, что объекты `Noddy` могут участвовать в циклических ссылках:10601061```c1062>>> import noddy21063>>> n = noddy2.Noddy()1064>>> l = [n]1065>>> n.first = l1066```10671068Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типы должны заполнить два слота и установить классовый флаг, который включает эти слоты:10691070```c1071#include <Python.h>1072#include "structmember.h"10731074typedef struct {1075 PyObject_HEAD1076 PyObject *first;1077 PyObject *last;1078 int number;1079} Noddy;10801081static int1082Noddy_traverse(Noddy *self, visitproc visit, void *arg)1083{1084 int vret;10851086 if (self->first) {1087 vret = visit(self->first, arg);1088 if (vret != 0)1089 return vret;1090 }1091 if (self->last) {1092 vret = visit(self->last, arg);1093 if (vret != 0)1094 return vret;1095 }10961097 return 0;1098}10991100static int1101Noddy_clear(Noddy *self)1102{1103 PyObject *tmp;11041105 tmp = self->first;1106 self->first = NULL;1107 Py_XDECREF(tmp);11081109 tmp = self->last;1110 self->last = NULL;1111 Py_XDECREF(tmp);11121113 return 0;1114}11151116static void1117Noddy_dealloc(Noddy* self)1118{1119 PyObject_GC_UnTrack(self);1120 Noddy_clear(self);1121 Py_TYPE(self)->tp_free((PyObject*)self);1122}11231124static PyObject *1125Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1126{1127 Noddy *self;11281129 self = (Noddy *)type->tp_alloc(type, 0);1130 if (self != NULL) {1131 self->first = PyString_FromString("");1132 if (self->first == NULL) {1133 Py_DECREF(self);1134 return NULL;1135 }11361137 self->last = PyString_FromString("");1138 if (self->last == NULL) {1139 Py_DECREF(self);1140 return NULL;1141 }11421143 self->number = 0;1144 }11451146 return (PyObject *)self;1147}11481149static int1150Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1151{1152 PyObject *first=NULL, *last=NULL, *tmp;11531154 static char *kwlist[] = {"first", "last", "number", NULL};11551156 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,1157 &first, &last,1158 &self->number))1159 return -1;11601161 if (first) {1162 tmp = self->first;1163 Py_INCREF(first);1164 self->first = first;1165 Py_XDECREF(tmp);1166 }11671168 if (last) {1169 tmp = self->last;1170 Py_INCREF(last);1171 self->last = last;1172 Py_XDECREF(tmp);1173 }11741175 return 0;1176}11771178static PyMemberDef Noddy_members[] = {1179 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1180 "first name"},1181 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1182 "last name"},1183 {"number", T_INT, offsetof(Noddy, number), 0,1184 "noddy number"},1185 {NULL} /* Страж */1186};11871188static PyObject *1189Noddy_name(Noddy* self)1190{1191 static PyObject *format = NULL;1192 PyObject *args, *result;11931194 if (format == NULL) {1195 format = PyString_FromString("%s %s");1196 if (format == NULL)1197 return NULL;1198 }11991200 if (self->first == NULL) {1201 PyErr_SetString(PyExc_AttributeError, "first");1202 return NULL;1203 }12041205 if (self->last == NULL) {1206 PyErr_SetString(PyExc_AttributeError, "last");1207 return NULL;1208 }12091210 args = Py_BuildValue("OO", self->first, self->last);1211 if (args == NULL)1212 return NULL;12131214 result = PyString_Format(format, args);1215 Py_DECREF(args);12161217 return result;1218}12191220static PyMethodDef Noddy_methods[] = {1221 {"name", (PyCFunction)Noddy_name, METH_NOARGS,1222 "Return the name, combining the first and last name"1223 },1224 {NULL} /* Страж */1225};12261227static PyTypeObject NoddyType = {1228 PyVarObject_HEAD_INIT(NULL, 0)1229 "noddy.Noddy", /* tp_name */1230 sizeof(Noddy), /* tp_basicsize */1231 0, /* tp_itemsize */1232 (destructor)Noddy_dealloc, /* tp_dealloc */1233 0, /* tp_print */1234 0, /* tp_getattr */1235 0, /* tp_setattr */1236 0, /* tp_compare */1237 0, /* tp_repr */1238 0, /* tp_as_number */1239 0, /* tp_as_sequence */1240 0, /* tp_as_mapping */1241 0, /* tp_hash */1242 0, /* tp_call */1243 0, /* tp_str */1244 0, /* tp_getattro */1245 0, /* tp_setattro */1246 0, /* tp_as_buffer */1247 Py_TPFLAGS_DEFAULT |1248 Py_TPFLAGS_BASETYPE |1249 Py_TPFLAGS_HAVE_GC, /* tp_flags */1250 "Noddy objects", /* tp_doc */1251 (traverseproc)Noddy_traverse, /* tp_traverse */1252 (inquiry)Noddy_clear, /* tp_clear */1253 0, /* tp_richcompare */1254 0, /* tp_weaklistoffset */1255 0, /* tp_iter */1256 0, /* tp_iternext */1257 Noddy_methods, /* tp_methods */1258 Noddy_members, /* tp_members */1259 0, /* tp_getset */1260 0, /* tp_base */1261 0, /* tp_dict */1262 0, /* tp_descr_get */1263 0, /* tp_descr_set */1264 0, /* tp_dictoffset */1265 (initproc)Noddy_init, /* tp_init */1266 0, /* tp_alloc */1267 Noddy_new, /* tp_new */1268};12691270static PyMethodDef module_methods[] = {1271 {NULL} /* Страж */1272};12731274#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */1275#define PyMODINIT_FUNC void1276#endif1277PyMODINIT_FUNC1278initnoddy4(void)1279{1280 PyObject* m;12811282 if (PyType_Ready(&NoddyType) < 0)1283 return;12841285 m = Py_InitModule3("noddy4", module_methods,1286 "Example module that creates an extension type.");12871288 if (m == NULL)1289 return;12901291 Py_INCREF(&NoddyType);1292 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1293}1294```12951296Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:12971298```c1299static int1300Noddy_traverse(Noddy *self, visitproc visit, void *arg)1301{1302 int vret;13031304 if (self->first) {1305 vret = visit(self->first, arg);1306 if (vret != 0)1307 return vret;1308 }1309 if (self->last) {1310 vret = visit(self->last, arg);1311 if (vret != 0)1312 return vret;1313 }13141315 return 0;1316}1317```13181319Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.13201321Python 2.4 и выше предоставляют макрос [`Py_VISIT()`](https://python-all.ru/2.7/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/2.7/c-api/gcsupport.html#c.Py_VISIT) можно упростить `Noddy_traverse()`:13221323```c1324static int1325Noddy_traverse(Noddy *self, visitproc visit, void *arg)1326{1327 Py_VISIT(self->first);1328 Py_VISIT(self->last);1329 return 0;1330}1331```13321333> **Примечание**1334>1335> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/2.7/c-api/gcsupport.html#c.Py_VISIT). Это делается для поощрения единообразия среди этих скучных реализаций.13361337Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах.13381339```c1340static int1341Noddy_clear(Noddy *self)1342{1343 PyObject *tmp;13441345 tmp = self->first;1346 self->first = NULL;1347 Py_XDECREF(tmp);13481349 tmp = self->last;1350 self->last = NULL;1351 Py_XDECREF(tmp);13521353 return 0;1354}1355```13561357Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы можно было установить каждый элемент в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это потому, что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратно объект. Кроме того, теперь, когда мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, запускающего сборку мусора. Если сборка мусора будет запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_traverse). Мы не можем рисковать тем, что функция `Noddy_traverse()` будет вызвана, когда счётчик ссылок элемента упал до нуля, а его значение ещё не установлено в *NULL*.13581359Python 2.4 и выше предоставляют [`Py_CLEAR()`](https://python-all.ru/2.7/c-api/refcounting.html#c.Py_CLEAR), автоматизирующий аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/2.7/c-api/refcounting.html#c.Py_CLEAR) функцию `Noddy_clear()` можно упростить:13601361```c1362static int1363Noddy_clear(Noddy *self)1364{1365 Py_CLEAR(self->first);1366 Py_CLEAR(self->last);1367 return 0;1368}1369```13701371Обратите внимание, что функция `Noddy_dealloc()` может вызывать произвольные функции через метод `__del__` или колбэк слабой ссылки. Это означает, что циклическая сборка мусора может быть запущена внутри функции. Поскольку сборщик мусора предполагает, что счётчик ссылок не равен нулю, необходимо отменить отслеживание объекта сборщиком мусора, вызвав [`PyObject_GC_UnTrack()`](https://python-all.ru/2.7/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой элементов. Вот переопределённый деаллокатор, который использует [`PyObject_GC_UnTrack()`](https://python-all.ru/2.7/c-api/gcsupport.html#c.PyObject_GC_UnTrack) и `Noddy_clear()`.13721373```c1374static void1375Noddy_dealloc(Noddy* self)1376{1377 PyObject_GC_UnTrack(self);1378 Noddy_clear(self);1379 Py_TYPE(self)->tp_free((PyObject*)self);1380}1381```13821383Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/2.7/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13841385```c1386Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1387```13881389Вот в основном и всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_free), их нужно было бы модифицировать для циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.13901391### 2.1.4. Наследование от других типов13921393Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему `PyTypeObject`. Совместное использование структур `PyTypeObject` между модулями расширений может быть затруднительным.13941395В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа `list`. Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.13961397```c1398>>> import shoddy1399>>> s = shoddy.Shoddy(range(3))1400>>> s.extend(s)1401>>> print len(s)140261403>>> print s.increment()140411405>>> print s.increment()140621407```14081409```c1410#include <Python.h>14111412typedef struct {1413 PyListObject list;1414 int state;1415} Shoddy;14161417static PyObject *1418Shoddy_increment(Shoddy *self, PyObject *unused)1419{1420 self->state++;1421 return PyInt_FromLong(self->state);1422}14231424static PyMethodDef Shoddy_methods[] = {1425 {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1426 PyDoc_STR("increment state counter")},1427 {NULL, NULL},1428};14291430static int1431Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1432{1433 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1434 return -1;1435 self->state = 0;1436 return 0;1437}14381439static PyTypeObject ShoddyType = {1440 PyVarObject_HEAD_INIT(NULL, 0)1441 "shoddy.Shoddy", /* tp_name */1442 sizeof(Shoddy), /* tp_basicsize */1443 0, /* tp_itemsize */1444 0, /* tp_dealloc */1445 0, /* tp_print */1446 0, /* tp_getattr */1447 0, /* tp_setattr */1448 0, /* tp_compare */1449 0, /* tp_repr */1450 0, /* tp_as_number */1451 0, /* tp_as_sequence */1452 0, /* tp_as_mapping */1453 0, /* tp_hash */1454 0, /* tp_call */1455 0, /* tp_str */1456 0, /* tp_getattro */1457 0, /* tp_setattro */1458 0, /* tp_as_buffer */1459 Py_TPFLAGS_DEFAULT |1460 Py_TPFLAGS_BASETYPE, /* tp_flags */1461 0, /* tp_doc */1462 0, /* tp_traverse */1463 0, /* tp_clear */1464 0, /* tp_richcompare */1465 0, /* tp_weaklistoffset */1466 0, /* tp_iter */1467 0, /* tp_iternext */1468 Shoddy_methods, /* tp_methods */1469 0, /* tp_members */1470 0, /* tp_getset */1471 0, /* tp_base */1472 0, /* tp_dict */1473 0, /* tp_descr_get */1474 0, /* tp_descr_set */1475 0, /* tp_dictoffset */1476 (initproc)Shoddy_init, /* tp_init */1477 0, /* tp_alloc */1478 0, /* tp_new */1479};14801481PyMODINIT_FUNC1482initshoddy(void)1483{1484 PyObject *m;14851486 ShoddyType.tp_base = &PyList_Type;1487 if (PyType_Ready(&ShoddyType) < 0)1488 return;14891490 m = Py_InitModule3("shoddy", NULL, "Shoddy module");1491 if (m == NULL)1492 return;14931494 Py_INCREF(&ShoddyType);1495 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1496}1497```14981499Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.15001501```c1502typedef struct {1503 PyListObject list;1504 int state;1505} Shoddy;1506```15071508Основное отличие объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже будет содержать [`PyObject_HEAD()`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.15091510Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно привести как к *PyListObject\**, так и к *Shoddy\**.15111512```c1513static int1514Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1515{1516 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1517 return -1;1518 self->state = 0;1519 return 0;1520}1521```15221523В методе `__init__` для нашего типа мы видим, как вызвать метод `__init__` базового типа.15241525Этот шаблон важен при написании типа с пользовательскими методами [`new`](https://python-all.ru/2.7/library/new.html#module-new) и `dealloc`. Метод [`new`](https://python-all.ru/2.7/library/new.html#module-new) не должен фактически выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_alloc) – это будет обработано базовым классом при вызове его метода [`tp_new`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_new).15261527При заполнении структуры [`PyTypeObject()`](https://python-all.ru/2.7/c-api/type.html#c.PyTypeObject) для типа `Shoddy` вы видите слот для `tp_base()`. Из-за проблем с кроссплатформенными компиляторами нельзя заполнять это поле напрямую с помощью [`PyList_Type()`](https://python-all.ru/2.7/c-api/list.html#c.PyList_Type); это можно сделать позже в функции `init()` модуля.15281529```c1530PyMODINIT_FUNC1531initshoddy(void)1532{1533 PyObject *m;15341535 ShoddyType.tp_base = &PyList_Type;1536 if (PyType_Ready(&ShoddyType) < 0)1537 return;15381539 m = Py_InitModule3("shoddy", NULL, "Shoddy module");1540 if (m == NULL)1541 return;15421543 Py_INCREF(&ShoddyType);1544 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1545}1546```15471548Перед вызовом [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании нового типа не обязательно заполнять слот [`tp_alloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_alloc) с помощью [`PyType_GenericNew()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.15491550После этого вызов [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Noddy`.15511552## 2.2. Методы типа15531554Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.15551556Здесь приводится определение [`PyTypeObject`](https://python-all.ru/2.7/c-api/type.html#c.PyTypeObject), некоторые поля, используемые только в отладочных сборках, опущены:15571558```c1559typedef struct _typeobject {1560 PyObject_VAR_HEAD1561 char *tp_name; /* Для вывода в формате "<module>.<name>" */1562 int tp_basicsize, tp_itemsize; /* Для выделения памяти */15631564 /* Методы для реализации стандартных операций */15651566 destructor tp_dealloc;1567 printfunc tp_print;1568 getattrfunc tp_getattr;1569 setattrfunc tp_setattr;1570 cmpfunc tp_compare;1571 reprfunc tp_repr;15721573 /* Наборы методов для стандартных классов */15741575 PyNumberMethods *tp_as_number;1576 PySequenceMethods *tp_as_sequence;1577 PyMappingMethods *tp_as_mapping;15781579 /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15801581 hashfunc tp_hash;1582 ternaryfunc tp_call;1583 reprfunc tp_str;1584 getattrofunc tp_getattro;1585 setattrofunc tp_setattro;15861587 /* Функции для доступа к объекту как к буферу ввода/вывода */1588 PyBufferProcs *tp_as_buffer;15891590 /* Флаги для определения наличия опциональных/расширенных возможностей */1591 long tp_flags;15921593 char *tp_doc; /* Строка документации */15941595 /* Назначенное значение в версии 2.0 */1596 /* вызов функции для всех доступных объектов */1597 traverseproc tp_traverse;15981599 /* удаление ссылок на содержащиеся объекты */1600 inquiry tp_clear;16011602 /* Назначенное значение в версии 2.1 */1603 /* расширенные сравнения */1604 richcmpfunc tp_richcompare;16051606 /* включение слабых ссылок */1607 long tp_weaklistoffset;16081609 /* Добавлено в версии 2.2 */1610 /* Итераторы */1611 getiterfunc tp_iter;1612 iternextfunc tp_iternext;16131614 /* Дескриптор атрибутов и механизмы подклассов */1615 struct PyMethodDef *tp_methods;1616 struct PyMemberDef *tp_members;1617 struct PyGetSetDef *tp_getset;1618 struct _typeobject *tp_base;1619 PyObject *tp_dict;1620 descrgetfunc tp_descr_get;1621 descrsetfunc tp_descr_set;1622 long tp_dictoffset;1623 initproc tp_init;1624 allocfunc tp_alloc;1625 newfunc tp_new;1626 freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1627 inquiry tp_is_gc; /* Для PyObject_IS_GC */1628 PyObject *tp_bases;1629 PyObject *tp_mro; /* порядок разрешения методов */1630 PyObject *tp_cache;1631 PyObject *tp_subclasses;1632 PyObject *tp_weaklist;16331634} PyTypeObject;1635```16361637Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.16381639Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.16401641```c1642char *tp_name; /* Для вывода */1643```16441645Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!16461647```c1648int tp_basicsize, tp_itemsize; /* Для выделения памяти */1649```16501651Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов этого типа. Python имеет встроенную поддержку структур переменной длины (например, строки, списки), и здесь появляется поле [`tp_itemsize`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_itemsize). Это будет рассмотрено позже.16521653```c1654char *tp_doc;1655```16561657Здесь можно указать строку (или её адрес), которая будет возвращена, когда скрипт Python обратится к `obj.__doc__` для получения строки документации.16581659Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16601661### 2.2.1. Завершение и освобождение памяти16621663```c1664destructor tp_dealloc;1665```16661667Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа уменьшается до нуля и интерпретатор Python хочет освободить его. Если ваш тип требует освобождения памяти или другой очистки, этот код можно разместить здесь. Сам объект также должен быть освобождён здесь. Вот пример такой функции:16681669```c1670static void1671newdatatype_dealloc(newdatatypeobject * obj)1672{1673 free(obj->obj_UnderlyingDatatypePtr);1674 Py_TYPE(obj)->tp_free(obj);1675}1676```16771678Важное требование к функции деаллокатора: она не должна вмешиваться в уже установленные исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при обычном возврате), ничего не делается для защиты деаллокаторов от того, что исключение уже установлено. Любые действия деаллокатора, которые могут вызвать выполнение дополнительного кода Python, могут обнаружить установленное исключение. Это может привести к вводящим в заблуждение ошибкам интерпретатора. Правильный способ защиты – сохранить текущее исключение перед выполнением небезопасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/2.7/c-api/exceptions.html#c.PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/2.7/c-api/exceptions.html#c.PyErr_Restore):16791680```c1681static void1682my_dealloc(PyObject *obj)1683{1684 MyObject *self = (MyObject *) obj;1685 PyObject *cbresult;16861687 if (self->my_callback != NULL) {1688 PyObject *err_type, *err_value, *err_traceback;1689 int have_error = PyErr_Occurred() ? 1 : 0;16901691 if (have_error)1692 PyErr_Fetch(&err_type, &err_value, &err_traceback);16931694 cbresult = PyObject_CallObject(self->my_callback, NULL);1695 if (cbresult == NULL)1696 PyErr_WriteUnraisable(self->my_callback);1697 else1698 Py_DECREF(cbresult);16991700 if (have_error)1701 PyErr_Restore(err_type, err_value, err_traceback);17021703 Py_DECREF(self->my_callback);1704 }1705 Py_TYPE(obj)->tp_free((PyObject*)self);1706}1707```17081709### 2.2.2. Представление объектов17101711В Python есть три способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/2.7/library/functions.html#repr) (или эквивалентный синтаксис с обратными кавычками), функция [`str()`](https://python-all.ru/2.7/library/functions.html#str) и инструкция [`print`](https://python-all.ru/2.7/reference/simple_stmts.html#print). Для большинства объектов инструкция [`print`](https://python-all.ru/2.7/reference/simple_stmts.html#print) эквивалентна функции [`str()`](https://python-all.ru/2.7/library/functions.html#str), но при необходимости можно настроить печать в `FILE*` особым образом; это следует делать только в том случае, если производительность является проблемой и профилирование показывает, что создание временного строкового объекта для записи в файл слишком дорого.17121713Все эти обработчики необязательны, и большинству типов достаточно реализовать обработчики [`tp_str`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_str) и [`tp_repr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_repr).17141715```c1716reprfunc tp_repr;1717reprfunc tp_str;1718printfunc tp_print;1719```17201721Обработчик [`tp_repr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызван. Вот простой пример:17221723```c1724static PyObject *1725newdatatype_repr(newdatatypeobject * obj)1726{1727 return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1728 obj->obj_UnderlyingDatatypePtr->size);1729}1730```17311732Если обработчик [`tp_repr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_repr) не задан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_name) типа и уникальный идентификатор объекта.17331734Обработчик [`tp_str`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_str) относится к [`str()`](https://python-all.ru/2.7/library/functions.html#str) так же, как описанный выше обработчик [`tp_repr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_repr) относится к [`repr()`](https://python-all.ru/2.7/library/functions.html#repr): он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/2.7/library/functions.html#str) для экземпляра вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_repr), но итоговая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_str) не указан, используется обработчик [`tp_repr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_repr).17351736Вот простой пример:17371738```c1739static PyObject *1740newdatatype_str(newdatatypeobject * obj)1741{1742 return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",1743 obj->obj_UnderlyingDatatypePtr->size);1744}1745```17461747Функция печати вызывается всякий раз, когда Python «печатает» экземпляр типа. Например, если 'node' – экземпляр типа TreeNode, то функция печати вызывается при выполнении такого кода Python:17481749```c1750print node1751```17521753Есть аргумент flags и один флаг `Py_PRINT_RAW`, который предлагает печатать без кавычек для строк и, возможно, без интерпретации escape-последовательностей.17541755Функция печати получает файловый объект в качестве аргумента. Вероятно, вы захотите писать именно в этот файловый объект.17561757Вот пример функции печати:17581759```c1760static int1761newdatatype_print(newdatatypeobject *obj, FILE *fp, int flags)1762{1763 if (flags & Py_PRINT_RAW) {1764 fprintf(fp, "<{newdatatype object--size: %d}>",1765 obj->obj_UnderlyingDatatypePtr->size);1766 }1767 else {1768 fprintf(fp, "\"<{newdatatype object--size: %d}>\"",1769 obj->obj_UnderlyingDatatypePtr->size);1770 }1771 return 0;1772}1773```17741775### 2.2.3. Управление атрибутами17761777Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.17781779Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, нужно реализовать функции только одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.17801781```c1782getattrfunc tp_getattr; /* char * version */1783setattrfunc tp_setattr;1784/* ... */1785getattrofunc tp_getattrofunc; /* PyObject * version */1786setattrofunc tp_setattrofunc;1787```17881789Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют универсальные реализации, которые можно использовать для предоставления версии [`PyObject*`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject) функций управления атрибутами. Необходимость в специфических для типа обработчиках атрибутов практически полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового универсального механизма.17901791#### 2.2.3.1. Общее управление атрибутами17921793Новое в версии 2.2.17941795Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:179617971. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready).17982. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17991800Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.18011802Когда вызывается [`PyType_Ready()`](https://python-all.ru/2.7/c-api/type.html#c.PyType_Ready), он использует три таблицы, на которые ссылается объект типа, для создания [дескрипторов](https://python-all.ru/2.7/glossary.html#term-descriptor), которые помещаются в словарь объекта типа. Каждый дескриптор управляет доступом к одному атрибуту объекта экземпляра. Каждая из таблиц необязательна; если все три равны *NULL*, экземпляры типа будут иметь только атрибуты, унаследованные от базового типа, и следует также установить поля [`tp_getattro`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_getattro) и [`tp_setattro`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_setattro) в *NULL*, позволяя базовому типу обрабатывать атрибуты.18031804Таблицы объявлены как три поля объекта типа:18051806```c1807struct PyMethodDef *tp_methods;1808struct PyMemberDef *tp_members;1809struct PyGetSetDef *tp_getset;1810```18111812Если [`tp_methods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_methods) не равен *NULL*, он должен ссылаться на массив структур [`PyMethodDef`](https://python-all.ru/2.7/c-api/structures.html#c.PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:18131814```c1815typedef struct PyMethodDef {1816 const char *ml_name; /* имя метода */1817 PyCFunction ml_meth; /* функция реализации */1818 int ml_flags; /* флаги */1819 const char *ml_doc; /* докстринг */1820} PyMethodDef;1821```18221823Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется одна дополнительная запись – это sentinel, отмечающий конец массива. Поле `ml_name` sentinel'а должно быть равно *NULL*.18241825XXX Нужно сослаться на какое-то единое обсуждение полей структуры, общее со следующим разделом.18261827Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:18281829```c1830typedef struct PyMemberDef {1831 char *name;1832 int type;1833 int offset;1834 int flags;1835 char *doc;1836} PyMemberDef;1837```18381839Для каждой записи в таблице будет создан [дескриптор](https://python-all.ru/2.7/glossary.html#term-descriptor) и добавлен к типу, который сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/2.7/library/functions.html#type) должно содержать один из кодов типов, определённых в заголовке `structmember.h`; значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.18401841XXX Нужно перенести часть этого в общий раздел!18421843Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.18441845| Константа | Значение |1846| --- | --- |1847| `READONLY` | Никогда не доступен для записи. |1848| `RO` | Сокращение для `READONLY`. |1849| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1850| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1851| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |18521853Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.18541855Как и в таблице [`tp_methods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_methods), требуется sentinel-запись со значением `name` равным *NULL*.18561857#### 2.2.3.2. Управление атрибутами для конкретного типа18581859Для простоты здесь будет продемонстрирована только версия `char*`; тип параметра name – единственное различие между вариантами интерфейса `char*` и [`PyObject*`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject). Этот пример делает то же самое, что и приведенный выше общий пример, но не использует общую поддержку, добавленную в Python 2.2. Ценность его демонстрации двояка: он показывает, как можно организовать базовое управление атрибутами способом, переносимым на более старые версии Python, и поясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, вы понимали, что нужно делать.18601861Обработчик [`tp_getattr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, что и метод [`__getattr__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__getattr__) класса.18621863Вероятный способ справиться с этим: (1) реализовать набор функций (таких как `newdatatype_getSize()` и `newdatatype_setSize()` в примере ниже), (2) предоставить таблицу методов, перечисляющую эти функции, и (3) предоставить функцию getattr, возвращающую результат поиска в этой таблице. Таблица методов использует ту же структуру, что и поле [`tp_methods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_methods) объекта типа.18641865Вот пример:18661867```c1868static PyMethodDef newdatatype_methods[] = {1869 {"getSize", (PyCFunction)newdatatype_getSize, METH_VARARGS,1870 "Return the current size."},1871 {"setSize", (PyCFunction)newdatatype_setSize, METH_VARARGS,1872 "Set the size."},1873 {NULL, NULL, 0, NULL} /* сторожевое значение */1874};18751876static PyObject *1877newdatatype_getattr(newdatatypeobject *obj, char *name)1878{1879 return Py_FindMethod(newdatatype_methods, (PyObject *)obj, name);1880}1881```18821883Обработчик [`tp_setattr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_setattr) вызывается, когда вызывается метод [`__setattr__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__delattr__) экземпляра класса. Если атрибут должен быть удален, третий параметр будет равен *NULL*. Вот пример, который просто вызывает исключение; если бы это действительно было все, что нужно, обработчик [`tp_setattr`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_setattr) следовало бы установить в *NULL*.18841885```c1886static int1887newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1888{1889 (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1890 return -1;1891}1892```18931894### 2.2.4. Сравнение объектов18951896```c1897cmpfunc tp_compare;1898```18991900Обработчик [`tp_compare`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_compare) вызывается, когда требуется сравнение, а объект не реализует соответствующий конкретный метод расширенного сравнения, подходящий для запрошенного сравнения. (Он всегда используется, если определён и используются функции [`PyObject_Compare()`](https://python-all.ru/2.7/c-api/object.html#c.PyObject_Compare) или [`PyObject_Cmp()`](https://python-all.ru/2.7/c-api/object.html#c.PyObject_Cmp), или если [`cmp()`](https://python-all.ru/2.7/library/functions.html#cmp) используется из Python.) Он аналогичен методу [`__cmp__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__cmp__). Эта функция должна возвращать `-1`, если *obj1* меньше *obj2*, `0`, если они равны, и `1`, если *obj1* больше *obj2*. (Ранее допускалось возвращать произвольные отрицательные или положительные целые числа для «меньше» и «больше» соответственно; начиная с Python 2.2 это больше не разрешено. В будущем другим возвращаемым значениям может быть присвоен иной смысл.)19011902Обработчик [`tp_compare`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_compare) может возбуждать исключение. В этом случае он должен вернуть отрицательное значение. Вызывающий код должен проверить наличие исключения с помощью [`PyErr_Occurred()`](https://python-all.ru/2.7/c-api/exceptions.html#c.PyErr_Occurred).19031904Вот пример реализации:19051906```c1907static int1908newdatatype_compare(newdatatypeobject * obj1, newdatatypeobject * obj2)1909{1910 long result;19111912 if (obj1->obj_UnderlyingDatatypePtr->size <1913 obj2->obj_UnderlyingDatatypePtr->size) {1914 result = -1;1915 }1916 else if (obj1->obj_UnderlyingDatatypePtr->size >1917 obj2->obj_UnderlyingDatatypePtr->size) {1918 result = 1;1919 }1920 else {1921 result = 0;1922 }1923 return result;1924}1925```19261927### 2.2.5. Поддержка абстрактных протоколов19281929Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [Уровень абстрактных объектов](https://python-all.ru/2.7/c-api/abstract.html#abstract).19301931Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)19321933```c1934PyNumberMethods *tp_as_number;1935PySequenceMethods *tp_as_sequence;1936PyMappingMethods *tp_as_mapping;1937```19381939Если требуется, чтобы объект мог вести себя как число, последовательность или отображение, нужно поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyMappingMethods) соответственно. Заполнять эту структуру подходящими значениями – ваша задача. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.19401941```c1942hashfunc tp_hash;1943```19441945Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:19461947```c1948static long1949newdatatype_hash(newdatatypeobject *obj)1950{1951 long result;1952 result = obj->obj_UnderlyingDatatypePtr->size;1953 result = result * 3;1954 return result;1955}1956```19571958```c1959ternaryfunc tp_call;1960```19611962Эта функция вызывается, когда экземпляр вашего типа данных «вызывается», например, если `obj1` является экземпляром вашего типа данных и скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_call).19631964Эта функция принимает три аргумента:196519661. *arg1* – это экземпляр типа данных, который является объектом вызова. Если вызов – `obj1('hello')`, то *arg1* равен `obj1`.19672. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/2.7/c-api/arg.html#c.PyArg_ParseTuple).19683. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/2.7/c-api/arg.html#c.PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите исключение [`TypeError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.TypeError) с сообщением о том, что именованные аргументы не поддерживаются.19691970Вот бессистемный пример реализации функции call.19711972```c1973/* Реализовать функцию вызова.1974 * obj1 – экземпляр, принимающий вызов.1975 * obj2 – кортеж, содержащий аргументы вызова, в данном1976 * случае 3 строки.1977 */1978static PyObject *1979newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1980{1981 PyObject *result;1982 char *arg1;1983 char *arg2;1984 char *arg3;19851986 if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1987 return NULL;1988 }1989 result = PyString_FromFormat(1990 "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1991 obj->obj_UnderlyingDatatypePtr->size,1992 arg1, arg2, arg3);1993 printf("\%s", PyString_AS_STRING(result));1994 return result;1995}1996```19971998XXX сюда нужно добавить некоторые поля…19992000```c2001/* Добавлено в версии 2.2 */2002/* Итераторы */2003getiterfunc tp_iter;2004iternextfunc tp_iternext;2005```20062007Эти функции обеспечивают поддержку протокола итератора. Любой объект, который хочет поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.20082009Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), должен создаваться и возвращаться новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, и также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.20102011Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только деструктивно). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/2.7/library/exceptions.html#exceptions.StopIteration); отказ от исключения может дать несколько лучшую производительность. Если возникает реальная ошибка, он должен установить исключение и вернуть *NULL*.20122013### 2.2.6. Поддержка слабых ссылок20142015Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).20162017Чтобы объект мог быть слабо ссылаемым, расширение должно включить поле [`PyObject*`](https://python-all.ru/2.7/c-api/structures.html#c.PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано значением *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_weaklistoffset) соответствующего объекта типа на смещение этого поля. Например, тип экземпляра определяется следующей структурой:20182019```c2020typedef struct {2021 PyObject_HEAD2022 PyClassObject *in_class; /* Объект класса */2023 PyObject *in_dict; /* Словарь */2024 PyObject *in_weakreflist; /* Список слабых ссылок */2025} PyInstanceObject;2026```20272028Статически объявленный объект типа для экземпляров определяется следующим образом:20292030```c2031PyTypeObject PyInstance_Type = {2032 PyObject_HEAD_INIT(&PyType_Type)2033 0,2034 "module.instance",20352036 /* Многое опущено для краткости... */20372038 Py_TPFLAGS_DEFAULT, /* tp_flags */2039 0, /* tp_doc */2040 0, /* tp_traverse */2041 0, /* tp_clear */2042 0, /* tp_richcompare */2043 offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */2044};2045```20462047Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:20482049```c2050static PyObject *2051instance_new() {2052 /* Прочие детали инициализации опущены для краткости */20532054 self->in_weakreflist = NULL;20552056 return (PyObject *) self;2057}2058```20592060Единственное дополнение заключается в том, что деструктор должен вызывать менеджер слабых ссылок для очистки любых слабых ссылок. Это требуется только в том случае, если список слабых ссылок не равен *NULL*:20612062```c2063static void2064instance_dealloc(PyInstanceObject *inst)2065{2066 /* Выделить временные объекты, если нужно, но не начинать2067 уничтожение пока не начинать2068 */20692070 if (inst->in_weakreflist != NULL)2071 PyObject_ClearWeakRefs((PyObject *) inst);20722073 /* Продолжить уничтожение объекта обычным образом. */2074}2075```20762077### 2.2.7. Дополнительные рекомендации20782079Помните, что большинство этих функций можно опустить, в этом случае вы передаёте `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с исходным дистрибутивом Python.20802081Чтобы узнать, как реализовать любой конкретный метод для вашего нового типа данных, сделайте следующее: скачайте и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем ищите в файлах исходного кода на C `tp_` вместе с нужной функцией (например, `tp_print` или `tp_compare`). Вы найдёте примеры функции, которую хотите реализовать.20822083Когда требуется проверить, является ли объект экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/2.7/c-api/object.html#c.PyObject_TypeCheck). Пример её использования может выглядеть так:20842085```c2086if (! PyObject_TypeCheck(some_object, &MyType)) {2087 PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");2088 return NULL;2089}2090```20912092Сноски20932094**[1](https://python-all.ru/2.7/extending/newtypes.html#id2)**20952096Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой.20972098**[2](https://python-all.ru/2.7/extending/newtypes.html#id3)**20992100В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/2.7/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы для «открепления» объекта от сборки мусора, однако эти вызовы являются продвинутыми и здесь не рассматриваются.21012102**[3](https://python-all.ru/2.7/extending/newtypes.html#id4)**21032104Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы.21052106**[4](https://python-all.ru/2.7/extending/newtypes.html#id5)**21072108Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают.2109