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

newtypes.md

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

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