Документация Python неофициальный перевод

newtypes.md

2113 строк · 114.9 КБ · обычная страница · сырой текст · скачать

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