newtypes.md
1> **Источник:** https://python-all.ru/2.6/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.6/extending/newtypes.html).1617## 2.1. Основы1819Среда выполнения Python рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/2.6/c-api/structures.html#PyObject). Объект [`PyObject`](https://python-all.ru/2.6/c-api/structures.html#PyObject) не является очень значительным – он содержит только счетчик ссылок и указатель на «объект типа» этого объекта. Именно здесь происходит основное действие; объект типа определяет, какие (C) функции вызываются, когда, например, ищется атрибут объекта или объект умножается на другой. Эти C-функции называются «методами типа», чтобы отличать их от таких вещей, как `[].append` (которые мы называем «методами объекта»).2021Итак, чтобы определить новый тип объекта, необходимо создать новый объект типа.2223Такие вещи лучше всего объяснять на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип:2425```c26#include <Python.h>2728typedef struct {29 PyObject_HEAD30 /* Сюда помещаются поля, специфичные для типа. */31} noddy_NoddyObject;3233static PyTypeObject noddy_NoddyType = {34 PyObject_HEAD_INIT(NULL)35 0, /*ob_size*/36 "noddy.Noddy", /*tp_name*/37 sizeof(noddy_NoddyObject), /*tp_basicsize*/38 0, /*tp_itemsize*/39 0, /*tp_dealloc*/40 0, /*tp_print*/41 0, /*tp_getattr*/42 0, /*tp_setattr*/43 0, /*tp_compare*/44 0, /*tp_repr*/45 0, /*tp_as_number*/46 0, /*tp_as_sequence*/47 0, /*tp_as_mapping*/48 0, /*tp_hash */49 0, /*tp_call*/50 0, /*tp_str*/51 0, /*tp_getattro*/52 0, /*tp_setattro*/53 0, /*tp_as_buffer*/54 Py_TPFLAGS_DEFAULT, /*tp_flags*/55 "Noddy objects", /* tp_doc */56};5758static PyMethodDef noddy_methods[] = {59 {NULL} /* Страж */60};6162#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */63#define PyMODINIT_FUNC void64#endif65PyMODINIT_FUNC66initnoddy(void) 67{68 PyObject* m;6970 noddy_NoddyType.tp_new = PyType_GenericNew;71 if (PyType_Ready(&noddy_NoddyType) < 0)72 return;7374 m = Py_InitModule3("noddy", noddy_methods,75 "Example module that creates an extension type.");7677 Py_INCREF(&noddy_NoddyType);78 PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);79}80```8182Сразу это может показаться большим объёмом, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы.8384Первое, что будет новым:8586```c87typedef struct {88 PyObject_HEAD89} noddy_NoddyObject;90```9192Вот что будет содержать объект Noddy – в данном случае не больше, чем любой объект Python, а именно счетчик ссылок и указатель на объект типа. Это поля, которые предоставляет макрос `PyObject_HEAD`. Причина использования макроса – стандартизация расположения и возможность включения специальных отладочных полей в отладочных сборках. Обратите внимание, что после макроса `PyObject_HEAD` точка с запятой не ставится; она уже включена в определение макроса. Следует остерегаться случайного добавления точки с запятой; это легко сделать по привычке, и ваш компилятор может не пожаловаться, но у другого, скорее всего, возникнет ошибка! (В Windows известно, что MSVC считает это ошибкой и отказывается компилировать код.)9394Для сравнения давайте взглянем на соответствующее определение для стандартных целых чисел Python:9596```c97typedef struct {98 PyObject_HEAD99 long ob_ival;100} PyIntObject;101```102103Далее переходим к самому главному – объекту типа.104105```c106static PyTypeObject noddy_NoddyType = {107 PyObject_HEAD_INIT(NULL)108 0, /*ob_size*/109 "noddy.Noddy", /*tp_name*/110 sizeof(noddy_NoddyObject), /*tp_basicsize*/111 0, /*tp_itemsize*/112 0, /*tp_dealloc*/113 0, /*tp_print*/114 0, /*tp_getattr*/115 0, /*tp_setattr*/116 0, /*tp_compare*/117 0, /*tp_repr*/118 0, /*tp_as_number*/119 0, /*tp_as_sequence*/120 0, /*tp_as_mapping*/121 0, /*tp_hash */122 0, /*tp_call*/123 0, /*tp_str*/124 0, /*tp_getattro*/125 0, /*tp_setattro*/126 0, /*tp_as_buffer*/127 Py_TPFLAGS_DEFAULT, /*tp_flags*/128 "Noddy objects", /* tp_doc */129};130```131132Теперь, если вы заглянете в определение [`PyTypeObject`](https://python-all.ru/2.6/c-api/type.html#PyTypeObject) в `object.h`, то увидите, что оно содержит гораздо больше полей, чем показано выше. Остальные поля будут заполнены нулями компилятором C, и обычно их не указывают явно, если они не нужны.133134Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:135136```c137PyObject_HEAD_INIT(NULL)138```139140Эта строка – небольшой изъян; хотелось бы написать:141142```c143PyObject_HEAD_INIT(&PyType_Type)144```145146поскольку типом объекта типа является «type», но это не совсем соответствует стандарту C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready).147148```c1490, /* ob_size */150```151152Поле заголовка [`ob_size`](https://python-all.ru/2.6/c-api/typeobj.html#ob_size) не используется; его присутствие в структуре типа – исторический артефакт, который сохраняется для двоичной совместимости с расширениями, скомпилированными для старых версий Python. Всегда устанавливайте это поле в ноль.153154```c155"noddy.Noddy", /* tp_name */156```157158Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:159160```c161>>> "" + noddy.new_noddy()162Traceback (most recent call last):163 File "<stdin>", line 1, in ?164TypeError: cannot add type "noddy.Noddy" to string165```166167Обратите внимание, что имя является точечным и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы устанавливаем имя типа `noddy.Noddy`.168169```c170sizeof(noddy_NoddyObject), /* tp_basicsize */171```172173Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New()`](https://python-all.ru/2.6/c-api/allocation.html#PyObject_New).174175> **Примечание**176>177> Если вы хотите, чтобы ваш тип мог быть подклассом из Python, и ваш тип имеет такое же значение [`tp_basicsize`](https://python-all.ru/2.6/c-api/typeobj.html#tp_basicsize), как и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс Python вашего типа должен будет указывать ваш тип первым в своем `__bases__`, иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, убедившись, что ваш тип имеет большее значение [`tp_basicsize`](https://python-all.ru/2.6/c-api/typeobj.html#tp_basicsize), чем его базовый тип. В большинстве случаев это будет верно, потому что либо вашим базовым типом будет [`object`](https://python-all.ru/2.6/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.178179```c1800, /* tp_itemsize */181```182183Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.184185Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/2.6/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).186187```c188Py_TPFLAGS_DEFAULT, /*tp_flags*/189```190191Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые текущей версией Python.192193Строку документации для типа мы указываем в [`tp_doc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_doc).194195```c196"Noddy objects", /* tp_doc */197```198199Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.200201Пока что всё, что нам нужно уметь делать – создавать новые объекты `Noddy`. Чтобы разрешить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new). В данном случае можно просто использовать стандартную реализацию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/2.6/c-api/type.html#PyType_GenericNew). Хотелось бы просто присвоить её слоту [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new), но из соображений переносимости мы не можем этого сделать. На некоторых платформах или компиляторах нельзя статически инициализировать член структуры функцией, определённой в другом C-модуле, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready):202203```c204noddy_NoddyType.tp_new = PyType_GenericNew;205if (PyType_Ready(&noddy_NoddyType) < 0)206 return;207```208209Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!210211Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `initnoddy()`:212213```c214if (PyType_Ready(&noddy_NoddyType) < 0)215 return;216```217218Это инициализирует тип `Noddy`, заполняя ряд членов, включая [`ob_type`](https://python-all.ru/2.6/c-api/typeobj.html#ob_type), который изначально устанавливается в *NULL*.219220```c221PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);222```223224Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy`, вызывая класс `Noddy`:225226```c227>>> import noddy228>>> mynoddy = noddy.Noddy()229```230231Вот и всё! Осталось только собрать это; поместите приведённый выше код в файл с именем `noddy.c` и232233```c234from distutils.core import setup, Extension235setup(name="noddy", version="1.0",236 ext_modules=[Extension("noddy", ["noddy.c"])])237```238239в файл с именем `setup.py`; затем, набрав240241```c242$ python setup.py build243```244245в командной оболочке, вы получите файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поиграться с объектами Noddy.246247Было не так сложно, правда?248249Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.250251### 2.1.1. Добавление данных и методов к базовому примеру252253Расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Создадим новый модуль `noddy2`, который добавляет эти возможности:254255```c256#include <Python.h>257#include "structmember.h"258259typedef struct {260 PyObject_HEAD261 PyObject *first; /* имя */262 PyObject *last; /* фамилия */263 int number;264} Noddy;265266static void267Noddy_dealloc(Noddy* self)268{269 Py_XDECREF(self->first);270 Py_XDECREF(self->last);271 self->ob_type->tp_free((PyObject*)self);272}273274static PyObject *275Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)276{277 Noddy *self;278279 self = (Noddy *)type->tp_alloc(type, 0);280 if (self != NULL) {281 self->first = PyString_FromString("");282 if (self->first == NULL)283 {284 Py_DECREF(self);285 return NULL;286 }287 288 self->last = PyString_FromString("");289 if (self->last == NULL)290 {291 Py_DECREF(self);292 return NULL;293 }294295 self->number = 0;296 }297298 return (PyObject *)self;299}300301static int302Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)303{304 PyObject *first=NULL, *last=NULL, *tmp;305306 static char *kwlist[] = {"first", "last", "number", NULL};307308 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, 309 &first, &last, 310 &self->number))311 return -1; 312313 if (first) {314 tmp = self->first;315 Py_INCREF(first);316 self->first = first;317 Py_XDECREF(tmp);318 }319320 if (last) {321 tmp = self->last;322 Py_INCREF(last);323 self->last = last;324 Py_XDECREF(tmp);325 }326327 return 0;328}329330static PyMemberDef Noddy_members[] = {331 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,332 "first name"},333 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,334 "last name"},335 {"number", T_INT, offsetof(Noddy, number), 0,336 "noddy number"},337 {NULL} /* Страж */338};339340static PyObject *341Noddy_name(Noddy* self)342{343 static PyObject *format = NULL;344 PyObject *args, *result;345346 if (format == NULL) {347 format = PyString_FromString("%s %s");348 if (format == NULL)349 return NULL;350 }351352 if (self->first == NULL) {353 PyErr_SetString(PyExc_AttributeError, "first");354 return NULL;355 }356357 if (self->last == NULL) {358 PyErr_SetString(PyExc_AttributeError, "last");359 return NULL;360 }361362 args = Py_BuildValue("OO", self->first, self->last);363 if (args == NULL)364 return NULL;365366 result = PyString_Format(format, args);367 Py_DECREF(args);368 369 return result;370}371372static PyMethodDef Noddy_methods[] = {373 {"name", (PyCFunction)Noddy_name, METH_NOARGS,374 "Return the name, combining the first and last name"375 },376 {NULL} /* Страж */377};378379static PyTypeObject NoddyType = {380 PyObject_HEAD_INIT(NULL)381 0, /*ob_size*/382 "noddy.Noddy", /*tp_name*/383 sizeof(Noddy), /*tp_basicsize*/384 0, /*tp_itemsize*/385 (destructor)Noddy_dealloc, /*tp_dealloc*/386 0, /*tp_print*/387 0, /*tp_getattr*/388 0, /*tp_setattr*/389 0, /*tp_compare*/390 0, /*tp_repr*/391 0, /*tp_as_number*/392 0, /*tp_as_sequence*/393 0, /*tp_as_mapping*/394 0, /*tp_hash */395 0, /*tp_call*/396 0, /*tp_str*/397 0, /*tp_getattro*/398 0, /*tp_setattro*/399 0, /*tp_as_buffer*/400 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/401 "Noddy objects", /* tp_doc */402 0, /* tp_traverse */403 0, /* tp_clear */404 0, /* tp_richcompare */405 0, /* tp_weaklistoffset */406 0, /* tp_iter */407 0, /* tp_iternext */408 Noddy_methods, /* tp_methods */409 Noddy_members, /* tp_members */410 0, /* tp_getset */411 0, /* tp_base */412 0, /* tp_dict */413 0, /* tp_descr_get */414 0, /* tp_descr_set */415 0, /* tp_dictoffset */416 (initproc)Noddy_init, /* tp_init */417 0, /* tp_alloc */418 Noddy_new, /* tp_new */419};420421static PyMethodDef module_methods[] = {422 {NULL} /* Страж */423};424425#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */426#define PyMODINIT_FUNC void427#endif428PyMODINIT_FUNC429initnoddy2(void) 430{431 PyObject* m;432433 if (PyType_Ready(&NoddyType) < 0)434 return;435436 m = Py_InitModule3("noddy2", module_methods,437 "Example module that creates an extension type.");438439 if (m == NULL)440 return;441442 Py_INCREF(&NoddyType);443 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);444}445```446447Эта версия модуля содержит ряд изменений.448449Мы добавили дополнительный include:450451```c452#include <structmember.h>453```454455Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.456457Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа сокращено до `NoddyType`.458459Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* – это строки Python, содержащие имя и фамилию. Атрибут *number* – целое число.460461Структура объекта обновляется соответствующим образом:462463```c464typedef struct {465 PyObject_HEAD466 PyObject *first;467 PyObject *last;468 int number;469} Noddy;470```471472Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:473474```c475static void476Noddy_dealloc(Noddy* self)477{478 Py_XDECREF(self->first);479 Py_XDECREF(self->last);480 self->ob_type->tp_free((PyObject*)self);481}482```483484которое присваивается члену [`tp_dealloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_dealloc):485486```c487(destructor)Noddy_dealloc, /*tp_dealloc*/488```489490Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/2.6/c-api/refcounting.html#Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/2.6/c-api/typeobj.html#tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, потому что объект может быть экземпляром подкласса.491492Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:493494```c495static PyObject *496Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)497{498 Noddy *self;499500 self = (Noddy *)type->tp_alloc(type, 0);501 if (self != NULL) {502 self->first = PyString_FromString("");503 if (self->first == NULL)504 {505 Py_DECREF(self);506 return NULL;507 }508509 self->last = PyString_FromString("");510 if (self->last == NULL)511 {512 Py_DECREF(self);513 return NULL;514 }515516 self->number = 0;517 }518519 return (PyObject *)self;520}521```522523и устанавливаем его в член [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new):524525```c526Noddy_new, /* tp_new */527```528529Член new отвечает за создание (в отличие от инициализации) объектов типа. В Python он доступен как метод [`__new__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__new__). Подробное обсуждение метода [`__new__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__new__) приведено в статье «Unifying types and classes in Python». Одна из причин реализовать метод new – гарантировать начальные значения переменных экземпляра. В данном случае мы используем метод new, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/2.6/c-api/type.html#PyType_GenericNew) в качестве метода new, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/2.6/c-api/type.html#PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.530531The new method is a static method that is passed the type being instantiated and any arguments passed when the type was called, and that returns the new object created. New methods always accept positional and keyword arguments, but they often ignore the arguments, leaving the argument handling to initializer methods. Note that if the type supports subclassing, the type passed may not be the type being defined. The new method calls the tp\_alloc slot to allocate memory. We don’t fill the [`tp_alloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_alloc) slot ourselves. Rather [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready) fills it for us by inheriting it from our base class, which is [`object`](https://python-all.ru/2.6/library/functions.html#object) by default. Most types use the default allocation.532533> **Примечание**534>535> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new) базового типа или [`__new__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__new__)), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не сделать, подклассы вашего типа в Python, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/2.6/library/exceptions.html#exceptions.TypeError).)536537Мы предоставляем функцию инициализации:538539```c540static int541Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)542{543 PyObject *first=NULL, *last=NULL, *tmp;544545 static char *kwlist[] = {"first", "last", "number", NULL};546547 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,548 &first, &last,549 &self->number))550 return -1;551552 if (first) {553 tmp = self->first;554 Py_INCREF(first);555 self->first = first;556 Py_XDECREF(tmp);557 }558559 if (last) {560 tmp = self->last;561 Py_INCREF(last);562 self->last = last;563 Py_XDECREF(tmp);564 }565566 return 0;567}568```569570заполнив слот [`tp_init`](https://python-all.ru/2.6/c-api/typeobj.html#tp_init).571572```c573(initproc)Noddy_init, /* tp_init */574```575576Слот [`tp_init`](https://python-all.ru/2.6/c-api/typeobj.html#tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, нельзя гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке объектов и может быть переопределён. Наш инициализатор принимает аргументы для начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы.577578Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__init__) на наших объектах. Поэтому нужно быть особенно осторожными при присваивании новых значений. Например, можно было бы попытаться присвоить члену `first` значение так:579580```c581if (first) {582 Py_XDECREF(self->first);583 Py_INCREF(first);584 self->first = first;585}586```587588Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому он может быть объектом любого типа. У него может быть деструктор, который вызывает код, пытающийся обратиться к члену `first`. Чтобы быть предусмотрительными и защититься от такой возможности, мы почти всегда переприсваиваем члены перед уменьшением их счётчиков ссылок. Когда же этого делать не нужно?589590- когда точно известно, что счётчик ссылок больше 1591- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/2.6/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа592- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/2.6/extending/newtypes.html#id8)593594Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:595596```c597static PyMemberDef Noddy_members[] = {598 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,599 "first name"},600 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,601 "last name"},602 {"number", T_INT, offsetof(Noddy, number), 0,603 "noddy number"},604 {NULL} /* Страж */605};606```607608и поместите определения в слот [`tp_members`](https://python-all.ru/2.6/c-api/typeobj.html#tp_members):609610```c611Noddy_members, /* tp_members */612```613614Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [*Управление атрибутами общего назначения*](https://python-all.ru/2.6/extending/newtypes.html#generic-attribute-management) ниже.615616Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.617618Определяем один метод `name()`, который выводит имя объекта как объединение имени и фамилии.619620```c621static PyObject *622Noddy_name(Noddy* self)623{624 static PyObject *format = NULL;625 PyObject *args, *result;626627 if (format == NULL) {628 format = PyString_FromString("%s %s");629 if (format == NULL)630 return NULL;631 }632633 if (self->first == NULL) {634 PyErr_SetString(PyExc_AttributeError, "first");635 return NULL;636 }637638 if (self->last == NULL) {639 PyErr_SetString(PyExc_AttributeError, "last");640 return NULL;641 }642643 args = Py_BuildValue("OO", self->first, self->last);644 if (args == NULL)645 return NULL;646647 result = PyString_Format(format, args);648 Py_DECREF(args);649650 return result;651}652```653654Метод реализован как C-функция, которая принимает экземпляр `Noddy` (или подкласса `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр как первый аргумент. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в принятии кортежа позиционных аргументов или словаря именованных аргументов. Этот метод эквивалентен следующему методу Python:655656```c657def name(self):658 return "%s %s" % (self.first, self.last)659```660661Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше предотвратить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать, в следующем разделе.662663Теперь, когда мы определили метод, нужно создать массив определений методов:664665```c666static PyMethodDef Noddy_methods[] = {667 {"name", (PyCFunction)Noddy_name, METH_NOARGS,668 "Return the name, combining the first and last name"669 },670 {NULL} /* Страж */671};672```673674и назначаем их в слот [`tp_methods`](https://python-all.ru/2.6/c-api/typeobj.html#tp_methods):675676```c677Noddy_methods, /* tp_methods */678```679680Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/2.6/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаются аргументы.681682Наконец, сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы осторожно, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/2.6/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флагов нашего класса:683684```c685Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/686```687688Мы переименовываем `initnoddy()` в `initnoddy2()` и обновляем имя модуля, передаваемое функции [`Py_InitModule3()`](https://python-all.ru/2.6/c-api/allocation.html#Py_InitModule3).689690Наконец, обновляем наш файл `setup.py` для сборки нового модуля:691692```c693from distutils.core import setup, Extension694setup(name="noddy", version="1.0",695 ext_modules=[696 Extension("noddy", ["noddy.c"]),697 Extension("noddy2", ["noddy2.c"]),698 ])699```700701### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных702703В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.704705```c706#include <Python.h>707#include "structmember.h"708709typedef struct {710 PyObject_HEAD711 PyObject *first;712 PyObject *last;713 int number;714} Noddy;715716static void717Noddy_dealloc(Noddy* self)718{719 Py_XDECREF(self->first);720 Py_XDECREF(self->last);721 self->ob_type->tp_free((PyObject*)self);722}723724static PyObject *725Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)726{727 Noddy *self;728729 self = (Noddy *)type->tp_alloc(type, 0);730 if (self != NULL) {731 self->first = PyString_FromString("");732 if (self->first == NULL)733 {734 Py_DECREF(self);735 return NULL;736 }737 738 self->last = PyString_FromString("");739 if (self->last == NULL)740 {741 Py_DECREF(self);742 return NULL;743 }744745 self->number = 0;746 }747748 return (PyObject *)self;749}750751static int752Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)753{754 PyObject *first=NULL, *last=NULL, *tmp;755756 static char *kwlist[] = {"first", "last", "number", NULL};757758 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist, 759 &first, &last, 760 &self->number))761 return -1; 762763 if (first) {764 tmp = self->first;765 Py_INCREF(first);766 self->first = first;767 Py_DECREF(tmp);768 }769770 if (last) {771 tmp = self->last;772 Py_INCREF(last);773 self->last = last;774 Py_DECREF(tmp);775 }776777 return 0;778}779780static PyMemberDef Noddy_members[] = {781 {"number", T_INT, offsetof(Noddy, number), 0,782 "noddy number"},783 {NULL} /* Страж */784};785786static PyObject *787Noddy_getfirst(Noddy *self, void *closure)788{789 Py_INCREF(self->first);790 return self->first;791}792793static int794Noddy_setfirst(Noddy *self, PyObject *value, void *closure)795{796 if (value == NULL) {797 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");798 return -1;799 }800 801 if (! PyString_Check(value)) {802 PyErr_SetString(PyExc_TypeError, 803 "The first attribute value must be a string");804 return -1;805 }806 807 Py_DECREF(self->first);808 Py_INCREF(value);809 self->first = value; 810811 return 0;812}813814static PyObject *815Noddy_getlast(Noddy *self, void *closure)816{817 Py_INCREF(self->last);818 return self->last;819}820821static int822Noddy_setlast(Noddy *self, PyObject *value, void *closure)823{824 if (value == NULL) {825 PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");826 return -1;827 }828 829 if (! PyString_Check(value)) {830 PyErr_SetString(PyExc_TypeError, 831 "The last attribute value must be a string");832 return -1;833 }834 835 Py_DECREF(self->last);836 Py_INCREF(value);837 self->last = value; 838839 return 0;840}841842static PyGetSetDef Noddy_getseters[] = {843 {"first", 844 (getter)Noddy_getfirst, (setter)Noddy_setfirst,845 "first name",846 NULL},847 {"last", 848 (getter)Noddy_getlast, (setter)Noddy_setlast,849 "last name",850 NULL},851 {NULL} /* Страж */852};853854static PyObject *855Noddy_name(Noddy* self)856{857 static PyObject *format = NULL;858 PyObject *args, *result;859860 if (format == NULL) {861 format = PyString_FromString("%s %s");862 if (format == NULL)863 return NULL;864 }865866 args = Py_BuildValue("OO", self->first, self->last);867 if (args == NULL)868 return NULL;869870 result = PyString_Format(format, args);871 Py_DECREF(args);872 873 return result;874}875876static PyMethodDef Noddy_methods[] = {877 {"name", (PyCFunction)Noddy_name, METH_NOARGS,878 "Return the name, combining the first and last name"879 },880 {NULL} /* Страж */881};882883static PyTypeObject NoddyType = {884 PyObject_HEAD_INIT(NULL)885 0, /*ob_size*/886 "noddy.Noddy", /*tp_name*/887 sizeof(Noddy), /*tp_basicsize*/888 0, /*tp_itemsize*/889 (destructor)Noddy_dealloc, /*tp_dealloc*/890 0, /*tp_print*/891 0, /*tp_getattr*/892 0, /*tp_setattr*/893 0, /*tp_compare*/894 0, /*tp_repr*/895 0, /*tp_as_number*/896 0, /*tp_as_sequence*/897 0, /*tp_as_mapping*/898 0, /*tp_hash */899 0, /*tp_call*/900 0, /*tp_str*/901 0, /*tp_getattro*/902 0, /*tp_setattro*/903 0, /*tp_as_buffer*/904 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/905 "Noddy objects", /* tp_doc */906 0, /* tp_traverse */907 0, /* tp_clear */908 0, /* tp_richcompare */909 0, /* tp_weaklistoffset */910 0, /* tp_iter */911 0, /* tp_iternext */912 Noddy_methods, /* tp_methods */913 Noddy_members, /* tp_members */914 Noddy_getseters, /* tp_getset */915 0, /* tp_base */916 0, /* tp_dict */917 0, /* tp_descr_get */918 0, /* tp_descr_set */919 0, /* tp_dictoffset */920 (initproc)Noddy_init, /* tp_init */921 0, /* tp_alloc */922 Noddy_new, /* tp_new */923};924925static PyMethodDef module_methods[] = {926 {NULL} /* Страж */927};928929#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */930#define PyMODINIT_FUNC void931#endif932PyMODINIT_FUNC933initnoddy3(void) 934{935 PyObject* m;936937 if (PyType_Ready(&NoddyType) < 0)938 return;939940 m = Py_InitModule3("noddy3", module_methods,941 "Example module that creates an extension type.");942943 if (m == NULL)944 return;945946 Py_INCREF(&NoddyType);947 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);948}949```950951Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:952953```c954Noddy_getfirst(Noddy *self, void *closure)955{956 Py_INCREF(self->first);957 return self->first;958}959960static int961Noddy_setfirst(Noddy *self, PyObject *value, void *closure)962{963 if (value == NULL) {964 PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");965 return -1;966 }967968 if (! PyString_Check(value)) {969 PyErr_SetString(PyExc_TypeError,970 "The first attribute value must be a string");971 return -1;972 }973974 Py_DECREF(self->first);975 Py_INCREF(value);976 self->first = value;977978 return 0;979}980```981982Функции получения передаётся объект `Noddy` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это, например, можно использовать, чтобы позволить единому набору функций получения и установки решать, какой атрибут получать или устанавливать, на основе данных в замыкании.)983984Функции установки передаётся объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.985986Создаём массив структур `PyGetSetDef`:987988```c989static PyGetSetDef Noddy_getseters[] = {990 {"first",991 (getter)Noddy_getfirst, (setter)Noddy_setfirst,992 "first name",993 NULL},994 {"last",995 (getter)Noddy_getlast, (setter)Noddy_setlast,996 "last name",997 NULL},998 {NULL} /* Страж */999};1000```10011002и регистрируем его в слоте [`tp_getset`](https://python-all.ru/2.6/c-api/typeobj.html#tp_getset):10031004```c1005Noddy_getseters, /* tp_getset */1006```10071008для регистрации геттеров и сеттеров наших атрибутов.10091010Последний элемент структуры `PyGetSetDef` – это упомянутое выше замыкание. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.10111012Также удаляем определения членов для этих атрибутов:10131014```c1015static PyMemberDef Noddy_members[] = {1016 {"number", T_INT, offsetof(Noddy, number), 0,1017 "noddy number"},1018 {NULL} /* Страж */1019};1020```10211022Также необходимо обновить обработчик [`tp_init`](https://python-all.ru/2.6/c-api/typeobj.html#tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/2.6/extending/newtypes.html#id9):10231024```c1025static int1026Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1027{1028 PyObject *first=NULL, *last=NULL, *tmp;10291030 static char *kwlist[] = {"first", "last", "number", NULL};10311032 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,1033 &first, &last,1034 &self->number))1035 return -1;10361037 if (first) {1038 tmp = self->first;1039 Py_INCREF(first);1040 self->first = first;1041 Py_DECREF(tmp);1042 }10431044 if (last) {1045 tmp = self->last;1046 Py_INCREF(last);1047 self->last = last;1048 Py_DECREF(tmp);1049 }10501051 return 0;1052}1053```10541055Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значения *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/2.6/c-api/refcounting.html#Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/2.6/c-api/refcounting.html#Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность того, что инициализация этих членов в конструкторе не удалась.10561057Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как мы делали ранее, и добавляем дополнительное определение в файл `setup.py`.10581059### 2.1.3. Поддержка циклической сборки мусора10601061В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:10621063```c1064>>> l = []1065>>> l.append(l)1066>>> del l1067```10681069В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10701071Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/2.6/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:10721073```c1074>>> import noddy21075>>> n = noddy2.Noddy()1076>>> l = [n]1077>>> n.first = l1078```10791080Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типам необходимо заполнить два слота и установить флаг класса, включающий эти слоты:10811082```c1083#include <Python.h>1084#include "structmember.h"10851086typedef struct {1087 PyObject_HEAD1088 PyObject *first;1089 PyObject *last;1090 int number;1091} Noddy;10921093static int1094Noddy_traverse(Noddy *self, visitproc visit, void *arg)1095{1096 int vret;10971098 if (self->first) {1099 vret = visit(self->first, arg);1100 if (vret != 0)1101 return vret;1102 }1103 if (self->last) {1104 vret = visit(self->last, arg);1105 if (vret != 0)1106 return vret;1107 }11081109 return 0;1110}11111112static int 1113Noddy_clear(Noddy *self)1114{1115 PyObject *tmp;11161117 tmp = self->first;1118 self->first = NULL;1119 Py_XDECREF(tmp);11201121 tmp = self->last;1122 self->last = NULL;1123 Py_XDECREF(tmp);11241125 return 0;1126}11271128static void1129Noddy_dealloc(Noddy* self)1130{1131 Noddy_clear(self);1132 self->ob_type->tp_free((PyObject*)self);1133}11341135static PyObject *1136Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1137{1138 Noddy *self;11391140 self = (Noddy *)type->tp_alloc(type, 0);1141 if (self != NULL) {1142 self->first = PyString_FromString("");1143 if (self->first == NULL)1144 {1145 Py_DECREF(self);1146 return NULL;1147 }1148 1149 self->last = PyString_FromString("");1150 if (self->last == NULL)1151 {1152 Py_DECREF(self);1153 return NULL;1154 }11551156 self->number = 0;1157 }11581159 return (PyObject *)self;1160}11611162static int1163Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1164{1165 PyObject *first=NULL, *last=NULL, *tmp;11661167 static char *kwlist[] = {"first", "last", "number", NULL};11681169 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, 1170 &first, &last, 1171 &self->number))1172 return -1; 11731174 if (first) {1175 tmp = self->first;1176 Py_INCREF(first);1177 self->first = first;1178 Py_XDECREF(tmp);1179 }11801181 if (last) {1182 tmp = self->last;1183 Py_INCREF(last);1184 self->last = last;1185 Py_XDECREF(tmp);1186 }11871188 return 0;1189}11901191static PyMemberDef Noddy_members[] = {1192 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1193 "first name"},1194 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1195 "last name"},1196 {"number", T_INT, offsetof(Noddy, number), 0,1197 "noddy number"},1198 {NULL} /* Страж */1199};12001201static PyObject *1202Noddy_name(Noddy* self)1203{1204 static PyObject *format = NULL;1205 PyObject *args, *result;12061207 if (format == NULL) {1208 format = PyString_FromString("%s %s");1209 if (format == NULL)1210 return NULL;1211 }12121213 if (self->first == NULL) {1214 PyErr_SetString(PyExc_AttributeError, "first");1215 return NULL;1216 }12171218 if (self->last == NULL) {1219 PyErr_SetString(PyExc_AttributeError, "last");1220 return NULL;1221 }12221223 args = Py_BuildValue("OO", self->first, self->last);1224 if (args == NULL)1225 return NULL;12261227 result = PyString_Format(format, args);1228 Py_DECREF(args);1229 1230 return result;1231}12321233static PyMethodDef Noddy_methods[] = {1234 {"name", (PyCFunction)Noddy_name, METH_NOARGS,1235 "Return the name, combining the first and last name"1236 },1237 {NULL} /* Страж */1238};12391240static PyTypeObject NoddyType = {1241 PyObject_HEAD_INIT(NULL)1242 0, /*ob_size*/1243 "noddy.Noddy", /*tp_name*/1244 sizeof(Noddy), /*tp_basicsize*/1245 0, /*tp_itemsize*/1246 (destructor)Noddy_dealloc, /*tp_dealloc*/1247 0, /*tp_print*/1248 0, /*tp_getattr*/1249 0, /*tp_setattr*/1250 0, /*tp_compare*/1251 0, /*tp_repr*/1252 0, /*tp_as_number*/1253 0, /*tp_as_sequence*/1254 0, /*tp_as_mapping*/1255 0, /*tp_hash */1256 0, /*tp_call*/1257 0, /*tp_str*/1258 0, /*tp_getattro*/1259 0, /*tp_setattro*/1260 0, /*tp_as_buffer*/1261 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/1262 "Noddy objects", /* tp_doc */1263 (traverseproc)Noddy_traverse, /* tp_traverse */1264 (inquiry)Noddy_clear, /* tp_clear */1265 0, /* tp_richcompare */1266 0, /* tp_weaklistoffset */1267 0, /* tp_iter */1268 0, /* tp_iternext */1269 Noddy_methods, /* tp_methods */1270 Noddy_members, /* tp_members */1271 0, /* tp_getset */1272 0, /* tp_base */1273 0, /* tp_dict */1274 0, /* tp_descr_get */1275 0, /* tp_descr_set */1276 0, /* tp_dictoffset */1277 (initproc)Noddy_init, /* tp_init */1278 0, /* tp_alloc */1279 Noddy_new, /* tp_new */1280};12811282static PyMethodDef module_methods[] = {1283 {NULL} /* Страж */1284};12851286#ifndef PyMODINIT_FUNC /* объявления для импорта/экспорта DLL */1287#define PyMODINIT_FUNC void1288#endif1289PyMODINIT_FUNC1290initnoddy4(void) 1291{1292 PyObject* m;12931294 if (PyType_Ready(&NoddyType) < 0)1295 return;12961297 m = Py_InitModule3("noddy4", module_methods,1298 "Example module that creates an extension type.");12991300 if (m == NULL)1301 return;13021303 Py_INCREF(&NoddyType);1304 PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1305}1306```13071308Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:13091310```c1311static int1312Noddy_traverse(Noddy *self, visitproc visit, void *arg)1313{1314 int vret;13151316 if (self->first) {1317 vret = visit(self->first, arg);1318 if (vret != 0)1319 return vret;1320 }1321 if (self->last) {1322 vret = visit(self->last, arg);1323 if (vret != 0)1324 return vret;1325 }13261327 return 0;1328}1329```13301331Для каждого подобъекта, который может участвовать в циклах, необходимо вызвать функцию `visit()`, которая передаётся методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целочисленное значение, которое должно быть возвращено, если оно не равно нулю.13321333Python 2.4 и выше предоставляют макрос [`Py_VISIT()`](https://python-all.ru/2.6/c-api/gcsupport.html#Py_VISIT), который автоматизирует вызов функций visit. С [`Py_VISIT()`](https://python-all.ru/2.6/c-api/gcsupport.html#Py_VISIT) функция `Noddy_traverse()` может быть упрощена:13341335```c1336static int1337Noddy_traverse(Noddy *self, visitproc visit, void *arg)1338{1339 Py_VISIT(self->first);1340 Py_VISIT(self->last);1341 return 0;1342}1343```13441345> **Примечание**1346>1347> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/2.6/c-api/typeobj.html#tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/2.6/c-api/gcsupport.html#Py_VISIT). Это сделано для поощрения единообразия в этих однообразных реализациях.13481349Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах. Реализуем этот метод и перереализуем деаллокатор для его использования:13501351```c1352static int1353Noddy_clear(Noddy *self)1354{1355 PyObject *tmp;13561357 tmp = self->first;1358 self->first = NULL;1359 Py_XDECREF(tmp);13601361 tmp = self->last;1362 self->last = NULL;1363 Py_XDECREF(tmp);13641365 return 0;1366}13671368static void1369Noddy_dealloc(Noddy* self)1370{1371 Noddy_clear(self);1372 self->ob_type->tp_free((PyObject*)self);1373}1374```13751376Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы установить каждый член в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это, потому что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратный вызов в объект. Кроме того, поскольку теперь мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, который запускает сборку мусора. Если сборка мусора запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/2.6/c-api/typeobj.html#tp_traverse). Мы не можем рисковать тем, что `Noddy_traverse()` будет вызвана, когда счётчик ссылок члена упал до нуля, а его значение ещё не установлено в *NULL*.13771378Python 2.4 и выше предоставляют макрос [`Py_CLEAR()`](https://python-all.ru/2.6/c-api/refcounting.html#Py_CLEAR), который автоматизирует аккуратное уменьшение счётчиков ссылок. С [`Py_CLEAR()`](https://python-all.ru/2.6/c-api/refcounting.html#Py_CLEAR) функция `Noddy_clear()` может быть упрощена:13791380```c1381static int1382Noddy_clear(Noddy *self)1383{1384 Py_CLEAR(self->first);1385 Py_CLEAR(self->last);1386 return 0;1387}1388```13891390Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/2.6/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13911392```c1393Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/1394```13951396В общем, это всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_alloc) или [`tp_free`](https://python-all.ru/2.6/c-api/typeobj.html#tp_free), их нужно было бы изменить для циклической сборки мусора. Большинство расширений будут использовать автоматически предоставленные версии.13971398### 2.1.4. Наследование от других типов13991400Можно создавать новые типы расширения, которые являются производными от существующих типов. Легче всего наследовать от встроенных типов, так как расширение может легко использовать необходимый ему [`PyTypeObject`](https://python-all.ru/2.6/c-api/type.html#PyTypeObject). Может быть сложно совместно использовать эти структуры [`PyTypeObject`](https://python-all.ru/2.6/c-api/type.html#PyTypeObject) между модулями расширения.14011402В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/2.6/library/functions.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.14031404```c1405>>> import shoddy1406>>> s = shoddy.Shoddy(range(3))1407>>> s.extend(s)1408>>> print len(s)140961410>>> print s.increment()141111412>>> print s.increment()141321414```14151416```c1417#include <Python.h>14181419typedef struct {1420 PyListObject list;1421 int state;1422} Shoddy;14231424static PyObject *1425Shoddy_increment(Shoddy *self, PyObject *unused)1426{1427 self->state++;1428 return PyInt_FromLong(self->state);1429}14301431static PyMethodDef Shoddy_methods[] = {1432 {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1433 PyDoc_STR("increment state counter")},1434 {NULL, NULL},1435};14361437static int1438Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1439{1440 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1441 return -1;1442 self->state = 0;1443 return 0;1444}14451446static PyTypeObject ShoddyType = {1447 PyObject_HEAD_INIT(NULL)1448 0, /* ob_size */1449 "shoddy.Shoddy", /* tp_name */1450 sizeof(Shoddy), /* tp_basicsize */1451 0, /* tp_itemsize */1452 0, /* tp_dealloc */1453 0, /* tp_print */1454 0, /* tp_getattr */1455 0, /* tp_setattr */1456 0, /* tp_compare */1457 0, /* tp_repr */1458 0, /* tp_as_number */1459 0, /* tp_as_sequence */1460 0, /* tp_as_mapping */1461 0, /* tp_hash */1462 0, /* tp_call */1463 0, /* tp_str */1464 0, /* tp_getattro */1465 0, /* tp_setattro */1466 0, /* tp_as_buffer */1467 Py_TPFLAGS_DEFAULT |1468 Py_TPFLAGS_BASETYPE, /* tp_flags */1469 0, /* tp_doc */1470 0, /* tp_traverse */1471 0, /* tp_clear */1472 0, /* tp_richcompare */1473 0, /* tp_weaklistoffset */1474 0, /* tp_iter */1475 0, /* tp_iternext */1476 Shoddy_methods, /* tp_methods */1477 0, /* tp_members */1478 0, /* tp_getset */1479 0, /* tp_base */1480 0, /* tp_dict */1481 0, /* tp_descr_get */1482 0, /* tp_descr_set */1483 0, /* tp_dictoffset */1484 (initproc)Shoddy_init, /* tp_init */1485 0, /* tp_alloc */1486 0, /* tp_new */1487};14881489PyMODINIT_FUNC1490initshoddy(void)1491{1492 PyObject *m;14931494 ShoddyType.tp_base = &PyList_Type;1495 if (PyType_Ready(&ShoddyType) < 0)1496 return;14971498 m = Py_InitModule3("shoddy", NULL, "Shoddy module");1499 if (m == NULL)1500 return;15011502 Py_INCREF(&ShoddyType);1503 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1504}1505```15061507Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.15081509```c1510typedef struct {1511 PyListObject list;1512 int state;1513} Shoddy;1514```15151516Основное различие для объектов производных типов заключается в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/2.6/c-api/structures.html#PyObject_HEAD) в начале своей структуры.15171518Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно приводить как к *PyListObject\**, так и к *Shoddy\**.15191520```c1521static int1522Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1523{1524 if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1525 return -1;1526 self->state = 0;1527 return 0;1528}1529```15301531В методе `__init__` нашего типа видно, как вызвать метод `__init__` базового типа.15321533Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен непосредственно выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_alloc) – это будет сделано базовым классом при вызове его [`tp_new`](https://python-all.ru/2.6/c-api/typeobj.html#tp_new).15341535При заполнении [`PyTypeObject()`](https://python-all.ru/2.6/c-api/type.html#PyTypeObject) для типа `Shoddy` вы видите слот для [`tp_base()`](https://python-all.ru/2.6/c-api/typeobj.html#tp_base). Из-за проблем с компиляторами на разных платформах вы не можете заполнить это поле напрямую с помощью [`PyList_Type()`](https://python-all.ru/2.6/c-api/list.html#PyList_Type); это можно сделать позже в функции `init()` модуля.15361537```c1538PyMODINIT_FUNC1539initshoddy(void)1540{1541 PyObject *m;15421543 ShoddyType.tp_base = &PyList_Type;1544 if (PyType_Ready(&ShoddyType) < 0)1545 return;15461547 m = Py_InitModule3("shoddy", NULL, "Shoddy module");1548 if (m == NULL)1549 return;15501551 Py_INCREF(&ShoddyType);1552 PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1553}1554```15551556Перед вызовом [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready) в структуре типа должен быть заполнен слот [`tp_base`](https://python-all.ru/2.6/c-api/typeobj.html#tp_base). При создании нового типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/2.6/c-api/type.html#PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.15571558После этого вызов [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready) и добавление объекта типа в модуль ничем не отличаются от базовых примеров с `Noddy`.15591560## 2.2. Методы типа15611562Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.15631564Ниже приведено определение [`PyTypeObject`](https://python-all.ru/2.6/c-api/type.html#PyTypeObject), в котором опущены некоторые поля, используемые только в отладочных сборках:15651566```c1567typedef struct _typeobject {1568 PyObject_VAR_HEAD1569 char *tp_name; /* Для вывода в формате "<module>.<name>" */1570 int tp_basicsize, tp_itemsize; /* Для выделения памяти */15711572 /* Методы для реализации стандартных операций */15731574 destructor tp_dealloc;1575 printfunc tp_print;1576 getattrfunc tp_getattr;1577 setattrfunc tp_setattr;1578 cmpfunc tp_compare;1579 reprfunc tp_repr;15801581 /* Наборы методов для стандартных классов */15821583 PyNumberMethods *tp_as_number;1584 PySequenceMethods *tp_as_sequence;1585 PyMappingMethods *tp_as_mapping;15861587 /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15881589 hashfunc tp_hash;1590 ternaryfunc tp_call;1591 reprfunc tp_str;1592 getattrofunc tp_getattro;1593 setattrofunc tp_setattro;15941595 /* Функции для доступа к объекту как к буферу ввода/вывода */1596 PyBufferProcs *tp_as_buffer;15971598 /* Флаги для определения наличия опциональных/расширенных возможностей */1599 long tp_flags;16001601 char *tp_doc; /* Строка документации */16021603 /* Назначенное значение в версии 2.0 */1604 /* вызов функции для всех доступных объектов */1605 traverseproc tp_traverse;16061607 /* удаление ссылок на содержащиеся объекты */1608 inquiry tp_clear;16091610 /* Назначенное значение в версии 2.1 */1611 /* расширенные сравнения */1612 richcmpfunc tp_richcompare;16131614 /* включение слабых ссылок */1615 long tp_weaklistoffset;16161617 /* Добавлено в версии 2.2 */1618 /* Итераторы */1619 getiterfunc tp_iter;1620 iternextfunc tp_iternext;16211622 /* Дескриптор атрибутов и механизмы подклассов */1623 struct PyMethodDef *tp_methods;1624 struct PyMemberDef *tp_members;1625 struct PyGetSetDef *tp_getset;1626 struct _typeobject *tp_base;1627 PyObject *tp_dict;1628 descrgetfunc tp_descr_get;1629 descrsetfunc tp_descr_set;1630 long tp_dictoffset;1631 initproc tp_init;1632 allocfunc tp_alloc;1633 newfunc tp_new;1634 freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1635 inquiry tp_is_gc; /* Для PyObject_IS_GC */1636 PyObject *tp_bases;1637 PyObject *tp_mro; /* порядок разрешения методов */1638 PyObject *tp_cache;1639 PyObject *tp_subclasses;1640 PyObject *tp_weaklist;16411642} PyTypeObject;1643```16441645Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.16461647Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.16481649```c1650char *tp_name; /* Для вывода */1651```16521653Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!16541655```c1656int tp_basicsize, tp_itemsize; /* Для выделения памяти */1657```16581659Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов данного типа. В Python есть встроенная поддержка структур переменной длины (например, строки, списки), для которой предназначено поле [`tp_itemsize`](https://python-all.ru/2.6/c-api/typeobj.html#tp_itemsize). Об этом будет рассказано позже.16601661```c1662char *tp_doc;1663```16641665Сюда можно поместить строку (или её адрес), которая должна возвращаться, когда скрипт на Python обращается к `obj.__doc__` для получения строки документации.16661667Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16681669### 2.2.1. Финализация и освобождение памяти16701671```c1672destructor tp_dealloc;1673```16741675Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа падает до нуля и интерпретатор Python хочет освободить его. Если у вашего типа есть память для освобождения или другие действия по очистке, поместите их сюда. Сам объект также должен быть освобождён здесь. Вот пример этой функции:16761677```c1678static void1679newdatatype_dealloc(newdatatypeobject * obj)1680{1681 free(obj->obj_UnderlyingDatatypePtr);1682 obj->ob_type->tp_free(obj);1683}1684```16851686Важное требование к функции деаллокатора – она не должна затрагивать ожидающие исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при нормальных возвратах), не предпринимается никаких действий для защиты деаллокаторов от того, что исключение уже установлено. Любые действия, выполняемые деаллокатором, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к ложным ошибкам интерпретатора. Правильный способ защиты – сохранить ожидающее исключение перед выполнением опасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/2.6/c-api/exceptions.html#PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/2.6/c-api/exceptions.html#PyErr_Restore):16871688```c1689static void1690my_dealloc(PyObject *obj)1691{1692 MyObject *self = (MyObject *) obj;1693 PyObject *cbresult;16941695 if (self->my_callback != NULL) {1696 PyObject *err_type, *err_value, *err_traceback;1697 int have_error = PyErr_Occurred() ? 1 : 0;16981699 if (have_error)1700 PyErr_Fetch(&err_type, &err_value, &err_traceback);17011702 cbresult = PyObject_CallObject(self->my_callback, NULL);1703 if (cbresult == NULL)1704 PyErr_WriteUnraisable(self->my_callback);1705 else1706 Py_DECREF(cbresult);17071708 if (have_error)1709 PyErr_Restore(err_type, err_value, err_traceback);17101711 Py_DECREF(self->my_callback);1712 }1713 obj->ob_type->tp_free((PyObject*)self);1714}1715```17161717### 2.2.2. Представление объекта17181719В Python есть три способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/2.6/library/functions.html#repr) (или эквивалентный синтаксис обратных кавычек), функция [`str()`](https://python-all.ru/2.6/library/functions.html#str) и инструкция [`print`](https://python-all.ru/2.6/reference/simple_stmts.html#print). Для большинства объектов инструкция [`print`](https://python-all.ru/2.6/reference/simple_stmts.html#print) эквивалентна функции [`str()`](https://python-all.ru/2.6/library/functions.html#str), но при необходимости можно сделать особый случай печати в `FILE*`; это следует делать только если эффективность является проблемой и профилирование показывает, что создание временного строкового объекта для записи в файл слишком дорого.17201721Все эти обработчики необязательны, и большинству типов нужно реализовать только обработчики [`tp_str`](https://python-all.ru/2.6/c-api/typeobj.html#tp_str) и [`tp_repr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_repr).17221723```c1724reprfunc tp_repr;1725reprfunc tp_str;1726printfunc tp_print;1727```17281729Обработчик [`tp_repr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызывается. Вот простой пример:17301731```c1732static PyObject *1733newdatatype_repr(newdatatypeobject * obj)1734{1735 return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1736 obj->obj_UnderlyingDatatypePtr->size);1737}1738```17391740Если обработчик [`tp_repr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_repr) не указан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/2.6/c-api/typeobj.html#tp_name) типа и уникальный идентификатор объекта.17411742Обработчик [`tp_str`](https://python-all.ru/2.6/c-api/typeobj.html#tp_str) для [`str()`](https://python-all.ru/2.6/library/functions.html#str) – это то же самое, что описанный выше обработчик [`tp_repr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_repr) для [`repr()`](https://python-all.ru/2.6/library/functions.html#repr); то есть он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/2.6/library/functions.html#str) на экземпляре вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_repr), но результирующая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/2.6/c-api/typeobj.html#tp_str) не указан, вместо него используется обработчик [`tp_repr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_repr).17431744Вот простой пример:17451746```c1747static PyObject *1748newdatatype_str(newdatatypeobject * obj)1749{1750 return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",1751 obj->obj_UnderlyingDatatypePtr->size);1752}1753```17541755Функция печати вызывается всякий раз, когда Python «печатает» экземпляр типа. Например, если 'node' – экземпляр типа TreeNode, то функция печати вызывается при выполнении такого кода Python:17561757```c1758print node1759```17601761Существует аргумент flags и один флаг `Py_PRINT_RAW`, который указывает печатать без строковых кавычек и, возможно, без интерпретации escape-последовательностей.17621763Функция печати получает файловый объект в качестве аргумента. Вероятно, вы захотите писать именно в этот файловый объект.17641765Вот пример функции печати:17661767```c1768static int1769newdatatype_print(newdatatypeobject *obj, FILE *fp, int flags)1770{1771 if (flags & Py_PRINT_RAW) {1772 fprintf(fp, "<{newdatatype object--size: %d}>",1773 obj->obj_UnderlyingDatatypePtr->size);1774 }1775 else {1776 fprintf(fp, "\"<{newdatatype object--size: %d}>\"",1777 obj->obj_UnderlyingDatatypePtr->size);1778 }1779 return 0;1780}1781```17821783### 2.2.3. Управление атрибутами17841785Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.17861787Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, достаточно реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/2.6/c-api/structures.html#PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.17881789```c1790getattrfunc tp_getattr; /* char * version */1791setattrfunc tp_setattr;1792/* ... */1793getattrofunc tp_getattrofunc; /* PyObject * version */1794setattrofunc tp_setattrofunc;1795```17961797Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют обобщённые реализации, которые можно использовать для предоставления версии функций управления атрибутами с [`PyObject*`](https://python-all.ru/2.6/c-api/structures.html#PyObject). Фактическая потребность в специфичных для типа обработчиках атрибутов почти полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового обобщённого механизма.17981799#### 2.2.3.1. Общее управление атрибутами18001801Новое в версии 2.2.18021803Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:180418051. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready).18062. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.18071808Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.18091810When [`PyType_Ready()`](https://python-all.ru/2.6/c-api/type.html#PyType_Ready) is called, it uses three tables referenced by the type object to create [*descriptor*](https://python-all.ru/2.6/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/2.6/c-api/typeobj.html#tp_getattro) and [`tp_setattro`](https://python-all.ru/2.6/c-api/typeobj.html#tp_setattro) fields *NULL* as well, allowing the base type to handle attributes.18111812Таблицы объявлены как три поля объекта типа:18131814```c1815struct PyMethodDef *tp_methods;1816struct PyMemberDef *tp_members;1817struct PyGetSetDef *tp_getset;1818```18191820Если [`tp_methods`](https://python-all.ru/2.6/c-api/typeobj.html#tp_methods) не равен *NULL*, он должен указывать на массив структур [`PyMethodDef`](https://python-all.ru/2.6/c-api/structures.html#PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:18211822```c1823typedef struct PyMethodDef {1824 char *ml_name; /* имя метода */1825 PyCFunction ml_meth; /* функция реализации */1826 int ml_flags; /* флаги */1827 char *ml_doc; /* докстринг */1828} PyMethodDef;1829```18301831Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется ещё одна запись – это сигнальный элемент, обозначающий конец массива. Поле `ml_name` сигнального элемента должно быть равно *NULL*.18321833XXX Нужно сослаться на какое-то единое обсуждение полей структуры, общее со следующим разделом.18341835Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:18361837```c1838typedef struct PyMemberDef {1839 char *name;1840 int type;1841 int offset;1842 int flags;1843 char *doc;1844} PyMemberDef;1845```18461847Для каждой записи в таблице будет создан [*дескриптор*](https://python-all.ru/2.6/glossary.html#term-descriptor) и добавлен к типу; он сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/2.6/library/functions.html#type) должно содержать один из кодов типа, определённых в заголовочном файле `structmember.h`; это значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.18481849XXX Нужно перенести часть этого в общий раздел!18501851Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.18521853| Константа | Значение |1854| --- | --- |1855| `READONLY` | Никогда не доступен для записи. |1856| `RO` | Сокращение для `READONLY`. |1857| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1858| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1859| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |18601861Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/2.6/c-api/typeobj.html#tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.18621863Как и в таблице [`tp_methods`](https://python-all.ru/2.6/c-api/typeobj.html#tp_methods), требуется сигнальный элемент со значением `name` равным *NULL*.18641865#### 2.2.3.2. Управление атрибутами, специфичное для типа18661867Для простоты здесь будет продемонстрирована только версия с `char*`; единственное различие между вариантами интерфейса с `char*` и [`PyObject*`](https://python-all.ru/2.6/c-api/structures.html#PyObject) – это тип параметра name. Этот пример по сути делает то же самое, что и общий пример выше, но не использует общую поддержку, добавленную в Python 2.2. Ценность его демонстрации двояка: он показывает, как базовое управление атрибутами может быть реализовано переносимым на старые версии Python способом, а также объясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, вы понимали, что нужно сделать.18681869Обработчик [`tp_getattr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, в которых вызывался бы метод [`__getattr__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__getattr__) класса.18701871Вероятный способ обработки: (1) реализовать набор функций (таких как `newdatatype_getSize()` и `newdatatype_setSize()` в примере ниже), (2) предоставить таблицу методов, перечисляющую эти функции, и (3) предоставить функцию getattr, возвращающую результат поиска в этой таблице. Таблица методов использует ту же структуру, что и поле [`tp_methods`](https://python-all.ru/2.6/c-api/typeobj.html#tp_methods) объекта типа.18721873Вот пример:18741875```c1876static PyMethodDef newdatatype_methods[] = {1877 {"getSize", (PyCFunction)newdatatype_getSize, METH_VARARGS,1878 "Return the current size."},1879 {"setSize", (PyCFunction)newdatatype_setSize, METH_VARARGS,1880 "Set the size."},1881 {NULL, NULL, 0, NULL} /* сторожевое значение */1882};18831884static PyObject *1885newdatatype_getattr(newdatatypeobject *obj, char *name)1886{1887 return Py_FindMethod(newdatatype_methods, (PyObject *)obj, name);1888}1889```18901891Обработчик [`tp_setattr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_setattr) вызывается при вызове метода [`__setattr__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__delattr__) экземпляра класса. Когда атрибут должен быть удалён, третий параметр будет равен *NULL*. Вот пример, который просто возбуждает исключение; если бы это действительно было всё, что нужно, обработчик [`tp_setattr`](https://python-all.ru/2.6/c-api/typeobj.html#tp_setattr) должен быть установлен в *NULL*.18921893```c1894static int1895newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1896{1897 (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1898 return -1;1899}1900```19011902### 2.2.4. Сравнение объектов19031904```c1905cmpfunc tp_compare;1906```19071908Обработчик [`tp_compare`](https://python-all.ru/2.6/c-api/typeobj.html#tp_compare) вызывается, когда требуется сравнение и объект не реализует конкретный метод богатого сравнения, соответствующий запрошенному сравнению. (Он всегда используется, если определён и используются функции [`PyObject_Compare()`](https://python-all.ru/2.6/c-api/object.html#PyObject_Compare) или [`PyObject_Cmp()`](https://python-all.ru/2.6/c-api/object.html#PyObject_Cmp), или если из Python используется [`cmp()`](https://python-all.ru/2.6/library/functions.html#cmp).) Он аналогичен методу [`__cmp__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__cmp__). Эта функция должна возвращать `-1`, если *obj1* меньше *obj2*, `0`, если они равны, и `1`, если *obj1* больше *obj2*. (Ранее допускалось возвращать произвольные отрицательные или положительные целые числа для «меньше» и «больше» соответственно; начиная с Python 2.2 это больше не разрешено. В будущем другие возвращаемые значения могут получить иной смысл.)19091910Обработчик [`tp_compare`](https://python-all.ru/2.6/c-api/typeobj.html#tp_compare) может возбуждать исключение. В этом случае он должен вернуть отрицательное значение. Вызывающий код должен проверить исключение с помощью [`PyErr_Occurred()`](https://python-all.ru/2.6/c-api/exceptions.html#PyErr_Occurred).19111912Вот пример реализации:19131914```c1915static int1916newdatatype_compare(newdatatypeobject * obj1, newdatatypeobject * obj2)1917{1918 long result;19191920 if (obj1->obj_UnderlyingDatatypePtr->size <1921 obj2->obj_UnderlyingDatatypePtr->size) {1922 result = -1;1923 }1924 else if (obj1->obj_UnderlyingDatatypePtr->size >1925 obj2->obj_UnderlyingDatatypePtr->size) {1926 result = 1;1927 }1928 else {1929 result = 0;1930 }1931 return result;1932}1933```19341935### 2.2.5. Поддержка абстрактных протоколов19361937Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [*Уровень абстрактных объектов*](https://python-all.ru/2.6/c-api/abstract.html#abstract).19381939Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)19401941```c1942PyNumberMethods tp_as_number;1943PySequenceMethods tp_as_sequence;1944PyMappingMethods tp_as_mapping;1945```19461947Если нужно, чтобы объект мог вести себя как число, последовательность или отображение, то следует поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/2.6/c-api/typeobj.html#PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/2.6/c-api/typeobj.html#PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/2.6/c-api/typeobj.html#PyMappingMethods) соответственно. Заполнить эту структуру подходящими значениями нужно самостоятельно. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.19481949```c1950hashfunc tp_hash;1951```19521953Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:19541955```c1956static long1957newdatatype_hash(newdatatypeobject *obj)1958{1959 long result;1960 result = obj->obj_UnderlyingDatatypePtr->size;1961 result = result * 3;1962 return result;1963}1964```19651966```c1967ternaryfunc tp_call;1968```19691970Эта функция вызывается, когда экземпляр вашего типа данных «вызывается». Например, если `obj1` – экземпляр вашего типа данных, а скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/2.6/c-api/typeobj.html#tp_call).19711972Эта функция принимает три аргумента:197319741. *arg1* – это экземпляр типа данных, который является субъектом вызова. Если вызовом является `obj1('hello')`, то *arg1* – это `obj1`.19752. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/2.6/c-api/arg.html#PyArg_ParseTuple).19763. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/2.6/c-api/arg.html#PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите [`TypeError`](https://python-all.ru/2.6/library/exceptions.html#exceptions.TypeError) с сообщением о том, что именованные аргументы не поддерживаются.19771978Вот бессистемный пример реализации функции call.19791980```c1981/* Реализовать функцию вызова.1982 * obj1 – экземпляр, принимающий вызов.1983 * obj2 – кортеж, содержащий аргументы вызова, в данном1984 * случае 3 строки.1985 */1986static PyObject *1987newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1988{1989 PyObject *result;1990 char *arg1;1991 char *arg2;1992 char *arg3;19931994 if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1995 return NULL;1996 }1997 result = PyString_FromFormat(1998 "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1999 obj->obj_UnderlyingDatatypePtr->size,2000 arg1, arg2, arg3);2001 printf("\%s", PyString_AS_STRING(result));2002 return result;2003}2004```20052006XXX некоторые поля нужно добавить сюда...20072008```c2009/* Добавлено в версии 2.2 */2010/* Итераторы */2011getiterfunc tp_iter;2012iternextfunc tp_iternext;2013```20142015Эти функции обеспечивают поддержку протокола итератора. Любой объект, который должен поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика: `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.20162017Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), следует создавать и возвращать новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, а также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.20182019Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только один раз). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/2.6/library/exceptions.html#exceptions.StopIteration); пропуск исключения может дать несколько лучшую производительность. Если произошла реальная ошибка, он должен установить исключение и вернуть *NULL*.20202021### 2.2.6. Поддержка слабых ссылок20222023Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).20242025Чтобы объект можно было использовать в слабых ссылках, расширение должно включить поле [`PyObject*`](https://python-all.ru/2.6/c-api/structures.html#PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/2.6/c-api/typeobj.html#tp_weaklistoffset) соответствующего объекта типа в смещение этого поля. Например, тип экземпляра определён следующей структурой:20262027```c2028typedef struct {2029 PyObject_HEAD2030 PyClassObject *in_class; /* Объект класса */2031 PyObject *in_dict; /* Словарь */2032 PyObject *in_weakreflist; /* Список слабых ссылок */2033} PyInstanceObject;2034```20352036Статически объявленный объект типа для экземпляров определяется следующим образом:20372038```c2039PyTypeObject PyInstance_Type = {2040 PyObject_HEAD_INIT(&PyType_Type)2041 0,2042 "module.instance",20432044 /* Многое опущено для краткости... */20452046 Py_TPFLAGS_DEFAULT, /* tp_flags */2047 0, /* tp_doc */2048 0, /* tp_traverse */2049 0, /* tp_clear */2050 0, /* tp_richcompare */2051 offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */2052};2053```20542055Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:20562057```c2058static PyObject *2059instance_new() {2060 /* Прочие детали инициализации опущены для краткости */20612062 self->in_weakreflist = NULL;20632064 return (PyObject *) self;2065}2066```20672068Единственное дополнение состоит в том, что деструктор должен вызвать менеджер слабых ссылок, чтобы очистить любые слабые ссылки. Это должно быть сделано до того, как произойдет любая другая часть разрушения, но требуется только в том случае, если список слабых ссылок не равен *NULL*:20692070```c2071static void2072instance_dealloc(PyInstanceObject *inst)2073{2074 /* Выделить временные объекты, если нужно, но не начинать2075 уничтожение пока не начинать2076 */20772078 if (inst->in_weakreflist != NULL)2079 PyObject_ClearWeakRefs((PyObject *) inst);20802081 /* Продолжить уничтожение объекта обычным образом. */2082}2083```20842085### 2.2.7. Дополнительные рекомендации20862087Помните, что большинство этих функций можно опустить; в этом случае нужно указать `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с дистрибутивом исходного кода Python.20882089Чтобы узнать, как реализовать конкретный метод для вашего нового типа данных, сделайте следующее: Загрузите и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем ищите в файлах исходного кода на C `tp_` плюс нужную функцию (например, `tp_print` или `tp_compare`). Вы найдёте примеры функции, которую хотите реализовать.20902091Когда нужно проверить, что объект является экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/2.6/c-api/object.html#PyObject_TypeCheck). Пример её использования может выглядеть следующим образом:20922093```c2094if (! PyObject_TypeCheck(some_object, &MyType)) {2095 PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");2096 return NULL;2097}2098```20992100Сноски21012102| [\[1\]](https://python-all.ru/2.6/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2103| --- | --- |21042105| [\[2\]](https://python-all.ru/2.6/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/2.6/c-api/typeobj.html#tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы, которые позволяют «открепить» объект от сборки мусора, однако эти вызовы являются продвинутыми и не рассматриваются здесь. |2106| --- | --- |21072108| [\[3\]](https://python-all.ru/2.6/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2109| --- | --- |21102111| [\[4\]](https://python-all.ru/2.6/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2112| --- | --- |2113