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

newtypes.md

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

1> **Источник:** https://python-all.ru/3.1/extending/newtypes.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 2. Определение новых типов89Как упоминалось в предыдущей главе, Python позволяет разработчику модуля расширения определять новые типы, которыми можно манипулировать из кода Python, подобно строкам и спискам в ядре Python.1011Это несложно; код всех типов расширений следует определённому шаблону, но есть несколько деталей, которые необходимо понять, прежде чем приступать к работе.1213## 2.1. Основы1415Среда выполнения Python рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/3.1/c-api/structures.html#PyObject). Объект [`PyObject`](https://python-all.ru/3.1/c-api/structures.html#PyObject) не является очень значительным – он содержит только счетчик ссылок и указатель на «объект типа» этого объекта. Именно здесь происходит основное действие; объект типа определяет, какие (C) функции вызываются, когда, например, ищется атрибут объекта или объект умножается на другой. Эти C-функции называются «методами типа», чтобы отличать их от таких вещей, как `[].append` (которые мы называем «методами объекта»).1617Итак, чтобы определить новый тип объекта, необходимо создать новый объект типа.1819Такие вещи лучше всего объяснять на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип:2021```c22#include <Python.h>2324typedef struct {25    PyObject_HEAD26    /* Сюда помещаются поля, специфичные для типа. */27} noddy_NoddyObject;2829static PyTypeObject noddy_NoddyType = {30    PyVarObject_HEAD_INIT(NULL, 0)31    "noddy.Noddy",             /* tp_name */32    sizeof(noddy_NoddyObject), /* tp_basicsize */33    0,                         /* tp_itemsize */34    0,                         /* tp_dealloc */35    0,                         /* tp_print */36    0,                         /* tp_getattr */37    0,                         /* tp_setattr */38    0,                         /* tp_reserved */39    0,                         /* tp_repr */40    0,                         /* tp_as_number */41    0,                         /* tp_as_sequence */42    0,                         /* tp_as_mapping */43    0,                         /* tp_hash  */44    0,                         /* tp_call */45    0,                         /* tp_str */46    0,                         /* tp_getattro */47    0,                         /* tp_setattro */48    0,                         /* tp_as_buffer */49    Py_TPFLAGS_DEFAULT,        /* tp_flags */50    "Noddy objects",           /* tp_doc */51};5253static PyModuleDef noddymodule = {54    PyModuleDef_HEAD_INIT,55    "noddy",56    "Example module that creates an extension type.",57    -1,58    NULL, NULL, NULL, NULL, NULL59};6061PyMODINIT_FUNC62PyInit_noddy(void) 63{64    PyObject* m;6566    noddy_NoddyType.tp_new = PyType_GenericNew;67    if (PyType_Ready(&noddy_NoddyType) < 0)68        return NULL;6970    m = PyModule_Create(&noddymodule);71    if (m == NULL)72        return NULL;7374    Py_INCREF(&noddy_NoddyType);75    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);76    return m;77}78```7980Сразу это может показаться большим объёмом, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы.8182Первое, что будет новым:8384```c85typedef struct {86    PyObject_HEAD87} noddy_NoddyObject;88```8990Вот что будет содержать объект Noddy – в данном случае не больше, чем любой объект Python, а именно счетчик ссылок и указатель на объект типа. Это поля, которые предоставляет макрос `PyObject_HEAD`. Причина использования макроса – стандартизация расположения и возможность включения специальных отладочных полей в отладочных сборках. Обратите внимание, что после макроса `PyObject_HEAD` точка с запятой не ставится; она уже включена в определение макроса. Следует остерегаться случайного добавления точки с запятой; это легко сделать по привычке, и ваш компилятор может не пожаловаться, но у другого, скорее всего, возникнет ошибка! (В Windows известно, что MSVC считает это ошибкой и отказывается компилировать код.)9192Для сравнения взгляните на соответствующее определение для стандартных чисел с плавающей запятой Python:9394```c95typedef struct {96    PyObject_HEAD97    double ob_fval;98} PyFloatObject;99```100101Далее переходим к самому главному – объекту типа.102103```c104static PyTypeObject noddy_NoddyType = {105    PyVarObject_HEAD_INIT(NULL, 0)106    "noddy.Noddy",             /* tp_name */107    sizeof(noddy_NoddyObject), /* tp_basicsize */108    0,                         /* tp_itemsize */109    0,                         /* tp_dealloc */110    0,                         /* tp_print */111    0,                         /* tp_getattr */112    0,                         /* tp_setattr */113    0,                         /* tp_reserved */114    0,                         /* tp_repr */115    0,                         /* tp_as_number */116    0,                         /* tp_as_sequence */117    0,                         /* tp_as_mapping */118    0,                         /* tp_hash  */119    0,                         /* tp_call */120    0,                         /* tp_str */121    0,                         /* tp_getattro */122    0,                         /* tp_setattro */123    0,                         /* tp_as_buffer */124    Py_TPFLAGS_DEFAULT,        /* tp_flags */125    "Noddy objects",           /* tp_doc */126};127```128129Теперь, если вы заглянете в определение [`PyTypeObject`](https://python-all.ru/3.1/c-api/type.html#PyTypeObject) в `object.h`, то увидите, что оно содержит гораздо больше полей, чем показано выше. Остальные поля будут заполнены нулями компилятором C, и обычно их не указывают явно, если они не нужны.130131Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:132133```c134PyVarObject_HEAD_INIT(NULL, 0)135```136137Эта строка – небольшой изъян; хотелось бы написать:138139```c140PyVarObject_HEAD_INIT(&PyType_Type, 0)141```142143поскольку типом объекта типа является «type», но это не совсем соответствует стандарту C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready).144145```c146"noddy.Noddy",              /* tp_name */147```148149Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:150151```c152>>> "" + noddy.new_noddy()153Traceback (most recent call last):154  File "<stdin>", line 1, in ?155TypeError: cannot add type "noddy.Noddy" to string156```157158Обратите внимание, что имя является точечным и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы устанавливаем имя типа `noddy.Noddy`.159160```c161sizeof(noddy_NoddyObject),  /* tp_basicsize */162```163164Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New()`](https://python-all.ru/3.1/c-api/allocation.html#PyObject_New).165166> **Примечание**167>168> Если вы хотите, чтобы ваш тип мог быть подклассом из Python, и ваш тип имеет такое же значение [`tp_basicsize`](https://python-all.ru/3.1/c-api/typeobj.html#tp_basicsize), как и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс Python вашего типа должен будет указывать ваш тип первым в своем `__bases__`, иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, убедившись, что ваш тип имеет большее значение [`tp_basicsize`](https://python-all.ru/3.1/c-api/typeobj.html#tp_basicsize), чем его базовый тип. В большинстве случаев это будет верно, потому что либо вашим базовым типом будет [`object`](https://python-all.ru/3.1/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.169170```c1710,                          /* tp_itemsize */172```173174Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.175176Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.1/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).177178```c179Py_TPFLAGS_DEFAULT,        /* tp_flags */180```181182Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые текущей версией Python.183184Строку документации для типа мы указываем в [`tp_doc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_doc).185186```c187"Noddy objects",           /* tp_doc */188```189190Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.191192Пока что всё, что нам нужно уметь делать – создавать новые объекты `Noddy`. Чтобы разрешить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new). В данном случае можно просто использовать стандартную реализацию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/3.1/c-api/type.html#PyType_GenericNew). Хотелось бы просто присвоить её слоту [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new), но из соображений переносимости мы не можем этого сделать. На некоторых платформах или компиляторах нельзя статически инициализировать член структуры функцией, определённой в другом C-модуле, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready):193194```c195noddy_NoddyType.tp_new = PyType_GenericNew;196if (PyType_Ready(&noddy_NoddyType) < 0)197    return;198```199200Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!201202Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_noddy()`:203204```c205if (PyType_Ready(&noddy_NoddyType) < 0)206    return;207```208209Это инициализирует тип `Noddy`, заполняя ряд членов, включая [`ob_type`](https://python-all.ru/3.1/c-api/typeobj.html#ob_type), который изначально устанавливается в *NULL*.210211```c212PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);213```214215Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy`, вызывая класс `Noddy`:216217```c218>>> import noddy219>>> mynoddy = noddy.Noddy()220```221222Вот и всё! Осталось только собрать это; поместите приведённый выше код в файл с именем `noddy.c` и223224```c225from distutils.core import setup, Extension226setup(name="noddy", version="1.0",227      ext_modules=[Extension("noddy", ["noddy.c"])])228```229230в файл с именем `setup.py`; затем, набрав231232```c233$ python setup.py build234```235236в командной оболочке, вы получите файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поиграться с объектами Noddy.237238Было не так сложно, правда?239240Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.241242### 2.1.1. Добавление данных и методов к базовому примеру243244Расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Создадим новый модуль `noddy2`, который добавляет эти возможности:245246```c247#include <Python.h>248#include "structmember.h"249250typedef struct {251    PyObject_HEAD252    PyObject *first; /* имя */253    PyObject *last;  /* фамилия */254    int number;255} Noddy;256257static void258Noddy_dealloc(Noddy* self)259{260    Py_XDECREF(self->first);261    Py_XDECREF(self->last);262    Py_TYPE(self)->tp_free((PyObject*)self);263}264265static PyObject *266Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)267{268    Noddy *self;269270    self = (Noddy *)type->tp_alloc(type, 0);271    if (self != NULL) {272        self->first = PyUnicode_FromString("");273        if (self->first == NULL)274          {275            Py_DECREF(self);276            return NULL;277          }278        279        self->last = PyUnicode_FromString("");280        if (self->last == NULL)281          {282            Py_DECREF(self);283            return NULL;284          }285286        self->number = 0;287    }288289    return (PyObject *)self;290}291292static int293Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)294{295    PyObject *first=NULL, *last=NULL, *tmp;296297    static char *kwlist[] = {"first", "last", "number", NULL};298299    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, 300                                      &first, &last, 301                                      &self->number))302        return -1; 303304    if (first) {305        tmp = self->first;306        Py_INCREF(first);307        self->first = first;308        Py_XDECREF(tmp);309    }310311    if (last) {312        tmp = self->last;313        Py_INCREF(last);314        self->last = last;315        Py_XDECREF(tmp);316    }317318    return 0;319}320321static PyMemberDef Noddy_members[] = {322    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,323     "first name"},324    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,325     "last name"},326    {"number", T_INT, offsetof(Noddy, number), 0,327     "noddy number"},328    {NULL}  /* Страж */329};330331static PyObject *332Noddy_name(Noddy* self)333{334    static PyObject *format = NULL;335    PyObject *args, *result;336337    if (format == NULL) {338        format = PyUnicode_FromString("%s %s");339        if (format == NULL)340            return NULL;341    }342343    if (self->first == NULL) {344        PyErr_SetString(PyExc_AttributeError, "first");345        return NULL;346    }347348    if (self->last == NULL) {349        PyErr_SetString(PyExc_AttributeError, "last");350        return NULL;351    }352353    args = Py_BuildValue("OO", self->first, self->last);354    if (args == NULL)355        return NULL;356357    result = PyUnicode_Format(format, args);358    Py_DECREF(args);359    360    return result;361}362363static PyMethodDef Noddy_methods[] = {364    {"name", (PyCFunction)Noddy_name, METH_NOARGS,365     "Return the name, combining the first and last name"366    },367    {NULL}  /* Страж */368};369370static PyTypeObject NoddyType = {371    PyVarObject_HEAD_INIT(NULL, 0)372    "noddy.Noddy",             /* tp_name */373    sizeof(Noddy),             /* tp_basicsize */374    0,                         /* tp_itemsize */375    (destructor)Noddy_dealloc, /* tp_dealloc */376    0,                         /* tp_print */377    0,                         /* tp_getattr */378    0,                         /* tp_setattr */379    0,                         /* tp_reserved */380    0,                         /* tp_repr */381    0,                         /* tp_as_number */382    0,                         /* tp_as_sequence */383    0,                         /* tp_as_mapping */384    0,                         /* tp_hash  */385    0,                         /* tp_call */386    0,                         /* tp_str */387    0,                         /* tp_getattro */388    0,                         /* tp_setattro */389    0,                         /* tp_as_buffer */390    Py_TPFLAGS_DEFAULT |391        Py_TPFLAGS_BASETYPE,   /* tp_flags */392    "Noddy objects",           /* tp_doc */393    0,		               /* tp_traverse */394    0,		               /* tp_clear */395    0,		               /* tp_richcompare */396    0,		               /* tp_weaklistoffset */397    0,		               /* tp_iter */398    0,		               /* tp_iternext */399    Noddy_methods,             /* tp_methods */400    Noddy_members,             /* tp_members */401    0,                         /* tp_getset */402    0,                         /* tp_base */403    0,                         /* tp_dict */404    0,                         /* tp_descr_get */405    0,                         /* tp_descr_set */406    0,                         /* tp_dictoffset */407    (initproc)Noddy_init,      /* tp_init */408    0,                         /* tp_alloc */409    Noddy_new,                 /* tp_new */410};411412static PyModuleDef noddy2module = {413    PyModuleDef_HEAD_INIT,414    "noddy2",415    "Example module that creates an extension type.",416    -1,417    NULL, NULL, NULL, NULL, NULL418};419420PyMODINIT_FUNC421PyInit_noddy2(void) 422{423    PyObject* m;424425    if (PyType_Ready(&NoddyType) < 0)426        return NULL;427428    m = PyModule_Create(&noddy2module);429    if (m == NULL)430        return NULL;431432    Py_INCREF(&NoddyType);433    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);434    return m;435}436```437438Эта версия модуля содержит ряд изменений.439440Мы добавили дополнительный include:441442```c443#include <structmember.h>444```445446Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.447448Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа сокращено до `NoddyType`.449450Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* – это строки Python, содержащие имя и фамилию. Атрибут *number* – целое число.451452Структура объекта обновляется соответствующим образом:453454```c455typedef struct {456    PyObject_HEAD457    PyObject *first;458    PyObject *last;459    int number;460} Noddy;461```462463Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:464465```c466static void467Noddy_dealloc(Noddy* self)468{469    Py_XDECREF(self->first);470    Py_XDECREF(self->last);471    Py_TYPE(self)->tp_free((PyObject*)self);472}473```474475которое присваивается члену [`tp_dealloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_dealloc):476477```c478(destructor)Noddy_dealloc, /*tp_dealloc*/479```480481Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/3.1/c-api/refcounting.html#Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.1/c-api/typeobj.html#tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, потому что объект может быть экземпляром подкласса.482483Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:484485```c486static PyObject *487Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)488{489    Noddy *self;490491    self = (Noddy *)type->tp_alloc(type, 0);492    if (self != NULL) {493        self->first = PyString_FromString("");494        if (self->first == NULL)495          {496            Py_DECREF(self);497            return NULL;498          }499500        self->last = PyString_FromString("");501        if (self->last == NULL)502          {503            Py_DECREF(self);504            return NULL;505          }506507        self->number = 0;508    }509510    return (PyObject *)self;511}512```513514и устанавливаем его в член [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new):515516```c517Noddy_new,                 /* tp_new */518```519520Член new отвечает за создание (в отличие от инициализации) объектов типа. В Python он доступен как метод [`__new__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__new__). Подробное обсуждение метода [`__new__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__new__) приведено в статье «Unifying types and classes in Python». Одна из причин реализовать метод new – гарантировать начальные значения переменных экземпляра. В данном случае мы используем метод new, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/3.1/c-api/type.html#PyType_GenericNew) в качестве метода new, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/3.1/c-api/type.html#PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.521522The new method is a static method that is passed the type being instantiated and any arguments passed when the type was called, and that returns the new object created. New methods always accept positional and keyword arguments, but they often ignore the arguments, leaving the argument handling to initializer methods. Note that if the type supports subclassing, the type passed may not be the type being defined. The new method calls the tp\_alloc slot to allocate memory. We don’t fill the [`tp_alloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_alloc) slot ourselves. Rather [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready) fills it for us by inheriting it from our base class, which is [`object`](https://python-all.ru/3.1/library/functions.html#object) by default. Most types use the default allocation.523524> **Примечание**525>526> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new) базового типа или [`__new__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__new__)), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не сделать, подклассы вашего типа в Python, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.1/library/exceptions.html#TypeError).)527528Мы предоставляем функцию инициализации:529530```c531static int532Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)533{534    PyObject *first=NULL, *last=NULL, *tmp;535536    static char *kwlist[] = {"first", "last", "number", NULL};537538    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,539                                      &first, &last,540                                      &self->number))541        return -1;542543    if (first) {544        tmp = self->first;545        Py_INCREF(first);546        self->first = first;547        Py_XDECREF(tmp);548    }549550    if (last) {551        tmp = self->last;552        Py_INCREF(last);553        self->last = last;554        Py_XDECREF(tmp);555    }556557    return 0;558}559```560561заполнив слот [`tp_init`](https://python-all.ru/3.1/c-api/typeobj.html#tp_init).562563```c564(initproc)Noddy_init,         /* tp_init */565```566567Слот [`tp_init`](https://python-all.ru/3.1/c-api/typeobj.html#tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, нельзя гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке объектов и может быть переопределён. Наш инициализатор принимает аргументы для начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы.568569Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__init__) на наших объектах. Поэтому нужно быть особенно осторожными при присваивании новых значений. Например, можно было бы попытаться присвоить члену `first` значение так:570571```c572if (first) {573    Py_XDECREF(self->first);574    Py_INCREF(first);575    self->first = first;576}577```578579Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому он может быть объектом любого типа. У него может быть деструктор, который вызывает код, пытающийся обратиться к члену `first`. Чтобы быть предусмотрительными и защититься от такой возможности, мы почти всегда переприсваиваем члены перед уменьшением их счётчиков ссылок. Когда же этого делать не нужно?580581- когда точно известно, что счётчик ссылок больше 1582- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.1/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа583- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.1/extending/newtypes.html#id8)584585Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:586587```c588static PyMemberDef Noddy_members[] = {589    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,590     "first name"},591    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,592     "last name"},593    {"number", T_INT, offsetof(Noddy, number), 0,594     "noddy number"},595    {NULL}  /* Страж */596};597```598599и поместите определения в слот [`tp_members`](https://python-all.ru/3.1/c-api/typeobj.html#tp_members):600601```c602Noddy_members,             /* tp_members */603```604605Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [*Управление атрибутами общего назначения*](https://python-all.ru/3.1/extending/newtypes.html#generic-attribute-management) ниже.606607Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.608609Определяем один метод `name()`, который выводит имя объекта как объединение имени и фамилии.610611```c612static PyObject *613Noddy_name(Noddy* self)614{615    static PyObject *format = NULL;616    PyObject *args, *result;617618    if (format == NULL) {619        format = PyString_FromString("%s %s");620        if (format == NULL)621            return NULL;622    }623624    if (self->first == NULL) {625        PyErr_SetString(PyExc_AttributeError, "first");626        return NULL;627    }628629    if (self->last == NULL) {630        PyErr_SetString(PyExc_AttributeError, "last");631        return NULL;632    }633634    args = Py_BuildValue("OO", self->first, self->last);635    if (args == NULL)636        return NULL;637638    result = PyString_Format(format, args);639    Py_DECREF(args);640641    return result;642}643```644645Метод реализован как C-функция, которая принимает экземпляр `Noddy` (или подкласса `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр как первый аргумент. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в принятии кортежа позиционных аргументов или словаря именованных аргументов. Этот метод эквивалентен следующему методу Python:646647```c648def name(self):649   return "%s %s" % (self.first, self.last)650```651652Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше предотвратить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать, в следующем разделе.653654Теперь, когда мы определили метод, нужно создать массив определений методов:655656```c657static PyMethodDef Noddy_methods[] = {658    {"name", (PyCFunction)Noddy_name, METH_NOARGS,659     "Return the name, combining the first and last name"660    },661    {NULL}  /* Страж */662};663```664665и назначаем их в слот [`tp_methods`](https://python-all.ru/3.1/c-api/typeobj.html#tp_methods):666667```c668Noddy_methods,             /* tp_methods */669```670671Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.1/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаются аргументы.672673Наконец, сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы осторожно, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.1/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флагов нашего класса:674675```c676Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/677```678679Переименовываем `PyInit_noddy()` в `PyInit_noddy2()` и обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.1/c-api/module.html#PyModuleDef).680681Наконец, обновляем наш файл `setup.py` для сборки нового модуля:682683```c684from distutils.core import setup, Extension685setup(name="noddy", version="1.0",686      ext_modules=[687         Extension("noddy", ["noddy.c"]),688         Extension("noddy2", ["noddy2.c"]),689         ])690```691692### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных693694В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.695696```c697#include <Python.h>698#include "structmember.h"699700typedef struct {701    PyObject_HEAD702    PyObject *first;703    PyObject *last;704    int number;705} Noddy;706707static void708Noddy_dealloc(Noddy* self)709{710    Py_XDECREF(self->first);711    Py_XDECREF(self->last);712    Py_TYPE(self)->tp_free((PyObject*)self);713}714715static PyObject *716Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)717{718    Noddy *self;719720    self = (Noddy *)type->tp_alloc(type, 0);721    if (self != NULL) {722        self->first = PyUnicode_FromString("");723        if (self->first == NULL)724          {725            Py_DECREF(self);726            return NULL;727          }728        729        self->last = PyUnicode_FromString("");730        if (self->last == NULL)731          {732            Py_DECREF(self);733            return NULL;734          }735736        self->number = 0;737    }738739    return (PyObject *)self;740}741742static int743Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)744{745    PyObject *first=NULL, *last=NULL, *tmp;746747    static char *kwlist[] = {"first", "last", "number", NULL};748749    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist, 750                                      &first, &last, 751                                      &self->number))752        return -1; 753754    if (first) {755        tmp = self->first;756        Py_INCREF(first);757        self->first = first;758        Py_DECREF(tmp);759    }760761    if (last) {762        tmp = self->last;763        Py_INCREF(last);764        self->last = last;765        Py_DECREF(tmp);766    }767768    return 0;769}770771static PyMemberDef Noddy_members[] = {772    {"number", T_INT, offsetof(Noddy, number), 0,773     "noddy number"},774    {NULL}  /* Страж */775};776777static PyObject *778Noddy_getfirst(Noddy *self, void *closure)779{780    Py_INCREF(self->first);781    return self->first;782}783784static int785Noddy_setfirst(Noddy *self, PyObject *value, void *closure)786{787  if (value == NULL) {788    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");789    return -1;790  }791  792  if (! PyUnicode_Check(value)) {793    PyErr_SetString(PyExc_TypeError, 794                    "The first attribute value must be a string");795    return -1;796  }797      798  Py_DECREF(self->first);799  Py_INCREF(value);800  self->first = value;    801802  return 0;803}804805static PyObject *806Noddy_getlast(Noddy *self, void *closure)807{808    Py_INCREF(self->last);809    return self->last;810}811812static int813Noddy_setlast(Noddy *self, PyObject *value, void *closure)814{815  if (value == NULL) {816    PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");817    return -1;818  }819  820  if (! PyUnicode_Check(value)) {821    PyErr_SetString(PyExc_TypeError, 822                    "The last attribute value must be a string");823    return -1;824  }825      826  Py_DECREF(self->last);827  Py_INCREF(value);828  self->last = value;    829830  return 0;831}832833static PyGetSetDef Noddy_getseters[] = {834    {"first", 835     (getter)Noddy_getfirst, (setter)Noddy_setfirst,836     "first name",837     NULL},838    {"last", 839     (getter)Noddy_getlast, (setter)Noddy_setlast,840     "last name",841     NULL},842    {NULL}  /* Страж */843};844845static PyObject *846Noddy_name(Noddy* self)847{848    static PyObject *format = NULL;849    PyObject *args, *result;850851    if (format == NULL) {852        format = PyUnicode_FromString("%s %s");853        if (format == NULL)854            return NULL;855    }856857    args = Py_BuildValue("OO", self->first, self->last);858    if (args == NULL)859        return NULL;860861    result = PyUnicode_Format(format, args);862    Py_DECREF(args);863    864    return result;865}866867static PyMethodDef Noddy_methods[] = {868    {"name", (PyCFunction)Noddy_name, METH_NOARGS,869     "Return the name, combining the first and last name"870    },871    {NULL}  /* Страж */872};873874static PyTypeObject NoddyType = {875    PyVarObject_HEAD_INIT(NULL, 0)876    "noddy.Noddy",             /* tp_name */877    sizeof(Noddy),             /* tp_basicsize */878    0,                         /* tp_itemsize */879    (destructor)Noddy_dealloc, /* tp_dealloc */880    0,                         /* tp_print */881    0,                         /* tp_getattr */882    0,                         /* tp_setattr */883    0,                         /* tp_reserved */884    0,                         /* tp_repr */885    0,                         /* tp_as_number */886    0,                         /* tp_as_sequence */887    0,                         /* tp_as_mapping */888    0,                         /* tp_hash  */889    0,                         /* tp_call */890    0,                         /* tp_str */891    0,                         /* tp_getattro */892    0,                         /* tp_setattro */893    0,                         /* tp_as_buffer */894    Py_TPFLAGS_DEFAULT |895        Py_TPFLAGS_BASETYPE,   /* tp_flags */896    "Noddy objects",           /* tp_doc */897    0,		               /* tp_traverse */898    0,		               /* tp_clear */899    0,		               /* tp_richcompare */900    0,		               /* tp_weaklistoffset */901    0,		               /* tp_iter */902    0,		               /* tp_iternext */903    Noddy_methods,             /* tp_methods */904    Noddy_members,             /* tp_members */905    Noddy_getseters,           /* tp_getset */906    0,                         /* tp_base */907    0,                         /* tp_dict */908    0,                         /* tp_descr_get */909    0,                         /* tp_descr_set */910    0,                         /* tp_dictoffset */911    (initproc)Noddy_init,      /* tp_init */912    0,                         /* tp_alloc */913    Noddy_new,                 /* tp_new */914};915916static PyModuleDef noddy3module = {917    PyModuleDef_HEAD_INIT,918    "noddy3",919    "Example module that creates an extension type.",920    -1,921    NULL, NULL, NULL, NULL, NULL922};923924PyMODINIT_FUNC925PyInit_noddy3(void) 926{927    PyObject* m;928929    if (PyType_Ready(&NoddyType) < 0)930        return NULL;931932    m = PyModule_Create(&noddy3module);933    if (m == NULL)934        return NULL;935936    Py_INCREF(&NoddyType);937    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);938    return m;939}940```941942Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:943944```c945Noddy_getfirst(Noddy *self, void *closure)946{947    Py_INCREF(self->first);948    return self->first;949}950951static int952Noddy_setfirst(Noddy *self, PyObject *value, void *closure)953{954  if (value == NULL) {955    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");956    return -1;957  }958959  if (! PyString_Check(value)) {960    PyErr_SetString(PyExc_TypeError,961                    "The first attribute value must be a string");962    return -1;963  }964965  Py_DECREF(self->first);966  Py_INCREF(value);967  self->first = value;968969  return 0;970}971```972973Функции получения передаётся объект `Noddy` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это, например, можно использовать, чтобы позволить единому набору функций получения и установки решать, какой атрибут получать или устанавливать, на основе данных в замыкании.)974975Функции установки передаётся объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.976977Создаём массив структур `PyGetSetDef`:978979```c980static PyGetSetDef Noddy_getseters[] = {981    {"first",982     (getter)Noddy_getfirst, (setter)Noddy_setfirst,983     "first name",984     NULL},985    {"last",986     (getter)Noddy_getlast, (setter)Noddy_setlast,987     "last name",988     NULL},989    {NULL}  /* Страж */990};991```992993и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.1/c-api/typeobj.html#tp_getset):994995```c996Noddy_getseters,           /* tp_getset */997```998999для регистрации геттеров и сеттеров наших атрибутов.10001001Последний элемент структуры `PyGetSetDef` – это упомянутое выше замыкание. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.10021003Также удаляем определения членов для этих атрибутов:10041005```c1006static PyMemberDef Noddy_members[] = {1007    {"number", T_INT, offsetof(Noddy, number), 0,1008     "noddy number"},1009    {NULL}  /* Страж */1010};1011```10121013Также необходимо обновить обработчик [`tp_init`](https://python-all.ru/3.1/c-api/typeobj.html#tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.1/extending/newtypes.html#id9):10141015```c1016static int1017Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1018{1019    PyObject *first=NULL, *last=NULL, *tmp;10201021    static char *kwlist[] = {"first", "last", "number", NULL};10221023    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,1024                                      &first, &last,1025                                      &self->number))1026        return -1;10271028    if (first) {1029        tmp = self->first;1030        Py_INCREF(first);1031        self->first = first;1032        Py_DECREF(tmp);1033    }10341035    if (last) {1036        tmp = self->last;1037        Py_INCREF(last);1038        self->last = last;1039        Py_DECREF(tmp);1040    }10411042    return 0;1043}1044```10451046Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значения *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.1/c-api/refcounting.html#Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.1/c-api/refcounting.html#Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность того, что инициализация этих членов в конструкторе не удалась.10471048Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как мы делали ранее, и добавляем дополнительное определение в файл `setup.py`.10491050### 2.1.3. Поддержка циклической сборки мусора10511052В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:10531054```c1055>>> l = []1056>>> l.append(l)1057>>> del l1058```10591060В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10611062Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.1/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:10631064```c1065>>> import noddy21066>>> n = noddy2.Noddy()1067>>> l = [n]1068>>> n.first = l1069```10701071Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типам необходимо заполнить два слота и установить флаг класса, включающий эти слоты:10721073```c1074#include <Python.h>1075#include "structmember.h"10761077typedef struct {1078    PyObject_HEAD1079    PyObject *first;1080    PyObject *last;1081    int number;1082} Noddy;10831084static int1085Noddy_traverse(Noddy *self, visitproc visit, void *arg)1086{1087    int vret;10881089    if (self->first) {1090        vret = visit(self->first, arg);1091        if (vret != 0)1092            return vret;1093    }1094    if (self->last) {1095        vret = visit(self->last, arg);1096        if (vret != 0)1097            return vret;1098    }10991100    return 0;1101}11021103static int 1104Noddy_clear(Noddy *self)1105{1106    PyObject *tmp;11071108    tmp = self->first;1109    self->first = NULL;1110    Py_XDECREF(tmp);11111112    tmp = self->last;1113    self->last = NULL;1114    Py_XDECREF(tmp);11151116    return 0;1117}11181119static void1120Noddy_dealloc(Noddy* self)1121{1122    Noddy_clear(self);1123    Py_TYPE(self)->tp_free((PyObject*)self);1124}11251126static PyObject *1127Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1128{1129    Noddy *self;11301131    self = (Noddy *)type->tp_alloc(type, 0);1132    if (self != NULL) {1133        self->first = PyUnicode_FromString("");1134        if (self->first == NULL)1135          {1136            Py_DECREF(self);1137            return NULL;1138          }1139        1140        self->last = PyUnicode_FromString("");1141        if (self->last == NULL)1142          {1143            Py_DECREF(self);1144            return NULL;1145          }11461147        self->number = 0;1148    }11491150    return (PyObject *)self;1151}11521153static int1154Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1155{1156    PyObject *first=NULL, *last=NULL, *tmp;11571158    static char *kwlist[] = {"first", "last", "number", NULL};11591160    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, 1161                                      &first, &last, 1162                                      &self->number))1163        return -1; 11641165    if (first) {1166        tmp = self->first;1167        Py_INCREF(first);1168        self->first = first;1169        Py_XDECREF(tmp);1170    }11711172    if (last) {1173        tmp = self->last;1174        Py_INCREF(last);1175        self->last = last;1176        Py_XDECREF(tmp);1177    }11781179    return 0;1180}11811182static PyMemberDef Noddy_members[] = {1183    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1184     "first name"},1185    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1186     "last name"},1187    {"number", T_INT, offsetof(Noddy, number), 0,1188     "noddy number"},1189    {NULL}  /* Страж */1190};11911192static PyObject *1193Noddy_name(Noddy* self)1194{1195    static PyObject *format = NULL;1196    PyObject *args, *result;11971198    if (format == NULL) {1199        format = PyUnicode_FromString("%s %s");1200        if (format == NULL)1201            return NULL;1202    }12031204    if (self->first == NULL) {1205        PyErr_SetString(PyExc_AttributeError, "first");1206        return NULL;1207    }12081209    if (self->last == NULL) {1210        PyErr_SetString(PyExc_AttributeError, "last");1211        return NULL;1212    }12131214    args = Py_BuildValue("OO", self->first, self->last);1215    if (args == NULL)1216        return NULL;12171218    result = PyUnicode_Format(format, args);1219    Py_DECREF(args);1220    1221    return result;1222}12231224static PyMethodDef Noddy_methods[] = {1225    {"name", (PyCFunction)Noddy_name, METH_NOARGS,1226     "Return the name, combining the first and last name"1227    },1228    {NULL}  /* Страж */1229};12301231static PyTypeObject NoddyType = {1232    PyVarObject_HEAD_INIT(NULL, 0)1233    "noddy.Noddy",             /* tp_name */1234    sizeof(Noddy),             /* tp_basicsize */1235    0,                         /* tp_itemsize */1236    (destructor)Noddy_dealloc, /* tp_dealloc */1237    0,                         /* tp_print */1238    0,                         /* tp_getattr */1239    0,                         /* tp_setattr */1240    0,                         /* tp_reserved */1241    0,                         /* tp_repr */1242    0,                         /* tp_as_number */1243    0,                         /* tp_as_sequence */1244    0,                         /* tp_as_mapping */1245    0,                         /* tp_hash  */1246    0,                         /* tp_call */1247    0,                         /* tp_str */1248    0,                         /* tp_getattro */1249    0,                         /* tp_setattro */1250    0,                         /* tp_as_buffer */1251    Py_TPFLAGS_DEFAULT |1252        Py_TPFLAGS_BASETYPE |1253        Py_TPFLAGS_HAVE_GC,    /* tp_flags */1254    "Noddy objects",           /* tp_doc */1255    (traverseproc)Noddy_traverse,   /* tp_traverse */1256    (inquiry)Noddy_clear,           /* tp_clear */1257    0,		               /* tp_richcompare */1258    0,		               /* tp_weaklistoffset */1259    0,		               /* tp_iter */1260    0,		               /* tp_iternext */1261    Noddy_methods,             /* tp_methods */1262    Noddy_members,             /* tp_members */1263    0,                         /* tp_getset */1264    0,                         /* tp_base */1265    0,                         /* tp_dict */1266    0,                         /* tp_descr_get */1267    0,                         /* tp_descr_set */1268    0,                         /* tp_dictoffset */1269    (initproc)Noddy_init,      /* tp_init */1270    0,                         /* tp_alloc */1271    Noddy_new,                 /* tp_new */1272};12731274static PyModuleDef noddy4module = {1275    PyModuleDef_HEAD_INIT,1276    "noddy4",1277    "Example module that creates an extension type.",1278    -1,1279    NULL, NULL, NULL, NULL, NULL1280};12811282PyMODINIT_FUNC1283PyInit_noddy4(void) 1284{1285    PyObject* m;12861287    if (PyType_Ready(&NoddyType) < 0)1288        return NULL;12891290    m = PyModule_Create(&noddy4module);1291    if (m == NULL)1292        return NULL;12931294    Py_INCREF(&NoddyType);1295    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1296    return m;1297}1298```12991300Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:13011302```c1303static int1304Noddy_traverse(Noddy *self, visitproc visit, void *arg)1305{1306    int vret;13071308    if (self->first) {1309        vret = visit(self->first, arg);1310        if (vret != 0)1311            return vret;1312    }1313    if (self->last) {1314        vret = visit(self->last, arg);1315        if (vret != 0)1316            return vret;1317    }13181319    return 0;1320}1321```13221323Для каждого подобъекта, который может участвовать в циклах, необходимо вызвать функцию `visit()`, которая передаётся методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целочисленное значение, которое должно быть возвращено, если оно не равно нулю.13241325Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.1/c-api/gcsupport.html#Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.1/c-api/gcsupport.html#Py_VISIT) функцию `Noddy_traverse()` можно упростить:13261327```c1328static int1329Noddy_traverse(Noddy *self, visitproc visit, void *arg)1330{1331    Py_VISIT(self->first);1332    Py_VISIT(self->last);1333    return 0;1334}1335```13361337> **Примечание**1338>1339> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/3.1/c-api/typeobj.html#tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.1/c-api/gcsupport.html#Py_VISIT). Это сделано для поощрения единообразия в этих однообразных реализациях.13401341Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах. Реализуем этот метод и перереализуем деаллокатор для его использования:13421343```c1344static int1345Noddy_clear(Noddy *self)1346{1347    PyObject *tmp;13481349    tmp = self->first;1350    self->first = NULL;1351    Py_XDECREF(tmp);13521353    tmp = self->last;1354    self->last = NULL;1355    Py_XDECREF(tmp);13561357    return 0;1358}13591360static void1361Noddy_dealloc(Noddy* self)1362{1363    Noddy_clear(self);1364    Py_TYPE(self)->tp_free((PyObject*)self);1365}1366```13671368Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы установить каждый член в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это, потому что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратный вызов в объект. Кроме того, поскольку теперь мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, который запускает сборку мусора. Если сборка мусора запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.1/c-api/typeobj.html#tp_traverse). Мы не можем рисковать тем, что `Noddy_traverse()` будет вызвана, когда счётчик ссылок члена упал до нуля, а его значение ещё не установлено в *NULL*.13691370Python предоставляет макрос [`Py_CLEAR()`](https://python-all.ru/3.1/c-api/refcounting.html#Py_CLEAR), автоматизирующий аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/3.1/c-api/refcounting.html#Py_CLEAR) функцию `Noddy_clear()` можно упростить:13711372```c1373static int1374Noddy_clear(Noddy *self)1375{1376    Py_CLEAR(self->first);1377    Py_CLEAR(self->last);1378    return 0;1379}1380```13811382Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.1/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13831384```c1385Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1386```13871388В общем, это всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_alloc) или [`tp_free`](https://python-all.ru/3.1/c-api/typeobj.html#tp_free), их нужно было бы изменить для циклической сборки мусора. Большинство расширений будут использовать автоматически предоставленные версии.13891390### 2.1.4. Наследование от других типов13911392Можно создавать новые типы расширения, которые являются производными от существующих типов. Легче всего наследовать от встроенных типов, так как расширение может легко использовать необходимый ему [`PyTypeObject`](https://python-all.ru/3.1/c-api/type.html#PyTypeObject). Может быть сложно совместно использовать эти структуры [`PyTypeObject`](https://python-all.ru/3.1/c-api/type.html#PyTypeObject) между модулями расширения.13931394В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.1/library/functions.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.13951396```c1397>>> import shoddy1398>>> s = shoddy.Shoddy(range(3))1399>>> s.extend(s)1400>>> print(len(s))140161402>>> print(s.increment())140311404>>> print(s.increment())140521406```14071408```c1409#include <Python.h>14101411typedef struct {1412    PyListObject list;1413    int state;1414} Shoddy;14151416static PyObject *1417Shoddy_increment(Shoddy *self, PyObject *unused)1418{1419    self->state++;1420    return PyLong_FromLong(self->state);1421}14221423static PyMethodDef Shoddy_methods[] = {1424    {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1425     PyDoc_STR("increment state counter")},1426    {NULL,	NULL},1427};14281429static int1430Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1431{1432    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1433        return -1;1434    self->state = 0;1435    return 0;1436}14371438static PyTypeObject ShoddyType = {1439    PyObject_HEAD_INIT(NULL)1440    "shoddy.Shoddy",         /* tp_name */1441    sizeof(Shoddy),          /* tp_basicsize */1442    0,                       /* tp_itemsize */1443    0,                       /* tp_dealloc */1444    0,                       /* tp_print */1445    0,                       /* tp_getattr */1446    0,                       /* tp_setattr */1447    0,                       /* tp_reserved */1448    0,                       /* tp_repr */1449    0,                       /* tp_as_number */1450    0,                       /* tp_as_sequence */1451    0,                       /* tp_as_mapping */1452    0,                       /* tp_hash */1453    0,                       /* tp_call */1454    0,                       /* tp_str */1455    0,                       /* tp_getattro */1456    0,                       /* tp_setattro */1457    0,                       /* tp_as_buffer */1458    Py_TPFLAGS_DEFAULT |1459        Py_TPFLAGS_BASETYPE, /* tp_flags */1460    0,                       /* tp_doc */1461    0,                       /* tp_traverse */1462    0,                       /* tp_clear */1463    0,                       /* tp_richcompare */1464    0,                       /* tp_weaklistoffset */1465    0,                       /* tp_iter */1466    0,                       /* tp_iternext */1467    Shoddy_methods,          /* tp_methods */1468    0,                       /* tp_members */1469    0,                       /* tp_getset */1470    0,                       /* tp_base */1471    0,                       /* tp_dict */1472    0,                       /* tp_descr_get */1473    0,                       /* tp_descr_set */1474    0,                       /* tp_dictoffset */1475    (initproc)Shoddy_init,   /* tp_init */1476    0,                       /* tp_alloc */1477    0,                       /* tp_new */1478};14791480static PyModuleDef shoddymodule = {1481    PyModuleDef_HEAD_INIT,1482    "shoddy",1483    "Shoddy module",1484    -1,1485    NULL, NULL, NULL, NULL, NULL1486};14871488PyMODINIT_FUNC1489PyInit_shoddy(void)1490{1491    PyObject *m;14921493    ShoddyType.tp_base = &PyList_Type;1494    if (PyType_Ready(&ShoddyType) < 0)1495        return NULL;14961497    m = PyModule_Create(&shoddymodule);1498    if (m == NULL)1499        return NULL;15001501    Py_INCREF(&ShoddyType);1502    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1503    return m;1504}1505```15061507Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.15081509```c1510typedef struct {1511    PyListObject list;1512    int state;1513} Shoddy;1514```15151516Основное различие для объектов производных типов заключается в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.1/c-api/structures.html#PyObject_HEAD) в начале своей структуры.15171518Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно приводить как к *PyListObject\**, так и к *Shoddy\**.15191520```c1521static int1522Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1523{1524    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1525       return -1;1526    self->state = 0;1527    return 0;1528}1529```15301531В методе `__init__` нашего типа видно, как вызвать метод `__init__` базового типа.15321533Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен непосредственно выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_alloc) – это будет сделано базовым классом при вызове его [`tp_new`](https://python-all.ru/3.1/c-api/typeobj.html#tp_new).15341535При заполнении [`PyTypeObject()`](https://python-all.ru/3.1/c-api/type.html#PyTypeObject) для типа `Shoddy` вы видите слот для [`tp_base()`](https://python-all.ru/3.1/c-api/typeobj.html#tp_base). Из-за проблем с компиляторами на разных платформах вы не можете заполнить это поле напрямую с помощью [`PyList_Type()`](https://python-all.ru/3.1/c-api/list.html#PyList_Type); это можно сделать позже в функции `init()` модуля.15361537```c1538PyMODINIT_FUNC1539PyInit_shoddy(void)1540{1541    PyObject *m;15421543    ShoddyType.tp_base = &PyList_Type;1544    if (PyType_Ready(&ShoddyType) < 0)1545        return NULL;15461547    m = PyModule_Create(&shoddymodule);1548    if (m == NULL)1549        return NULL;15501551    Py_INCREF(&ShoddyType);1552    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1553    return m;1554}1555```15561557Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready) в структуре типа должен быть заполнен слот [`tp_base`](https://python-all.ru/3.1/c-api/typeobj.html#tp_base). При создании нового типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.1/c-api/type.html#PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.15581559После этого вызов [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready) и добавление объекта типа в модуль ничем не отличаются от базовых примеров с `Noddy`.15601561## 2.2. Методы типа15621563Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.15641565Ниже приведено определение [`PyTypeObject`](https://python-all.ru/3.1/c-api/type.html#PyTypeObject), в котором опущены некоторые поля, используемые только в отладочных сборках:15661567```c1568typedef struct _typeobject {1569    PyObject_VAR_HEAD1570    char *tp_name; /* Для вывода в формате "<module>.<name>" */1571    int tp_basicsize, tp_itemsize; /* Для выделения памяти */15721573    /* Методы для реализации стандартных операций */15741575    destructor tp_dealloc;1576    printfunc tp_print;1577    getattrfunc tp_getattr;1578    setattrfunc tp_setattr;1579    void *tp_reserved;1580    reprfunc tp_repr;15811582    /* Наборы методов для стандартных классов */15831584    PyNumberMethods *tp_as_number;1585    PySequenceMethods *tp_as_sequence;1586    PyMappingMethods *tp_as_mapping;15871588    /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15891590    hashfunc tp_hash;1591    ternaryfunc tp_call;1592    reprfunc tp_str;1593    getattrofunc tp_getattro;1594    setattrofunc tp_setattro;15951596    /* Функции для доступа к объекту как к буферу ввода/вывода */1597    PyBufferProcs *tp_as_buffer;15981599    /* Флаги для определения наличия опциональных/расширенных возможностей */1600    long tp_flags;16011602    char *tp_doc; /* Строка документации */16031604    /* вызов функции для всех доступных объектов */1605    traverseproc tp_traverse;16061607    /* удаление ссылок на содержащиеся объекты */1608    inquiry tp_clear;16091610    /* расширенные сравнения */1611    richcmpfunc tp_richcompare;16121613    /* включение слабых ссылок */1614    long tp_weaklistoffset;16151616    /* Итераторы */1617    getiterfunc tp_iter;1618    iternextfunc tp_iternext;16191620    /* Дескриптор атрибутов и механизмы подклассов */1621    struct PyMethodDef *tp_methods;1622    struct PyMemberDef *tp_members;1623    struct PyGetSetDef *tp_getset;1624    struct _typeobject *tp_base;1625    PyObject *tp_dict;1626    descrgetfunc tp_descr_get;1627    descrsetfunc tp_descr_set;1628    long tp_dictoffset;1629    initproc tp_init;1630    allocfunc tp_alloc;1631    newfunc tp_new;1632    freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1633    inquiry tp_is_gc; /* Для PyObject_IS_GC */1634    PyObject *tp_bases;1635    PyObject *tp_mro; /* порядок разрешения методов */1636    PyObject *tp_cache;1637    PyObject *tp_subclasses;1638    PyObject *tp_weaklist;16391640} PyTypeObject;1641```16421643Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.16441645Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.16461647```c1648char *tp_name; /* Для вывода */1649```16501651Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!16521653```c1654int tp_basicsize, tp_itemsize; /* Для выделения памяти */1655```16561657Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов данного типа. В Python есть встроенная поддержка структур переменной длины (например, строки, списки), для которой предназначено поле [`tp_itemsize`](https://python-all.ru/3.1/c-api/typeobj.html#tp_itemsize). Об этом будет рассказано позже.16581659```c1660char *tp_doc;1661```16621663Сюда можно поместить строку (или её адрес), которая должна возвращаться, когда скрипт на Python обращается к `obj.__doc__` для получения строки документации.16641665Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16661667### 2.2.1. Финализация и освобождение памяти16681669```c1670destructor tp_dealloc;1671```16721673Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа падает до нуля и интерпретатор Python хочет освободить его. Если у вашего типа есть память для освобождения или другие действия по очистке, поместите их сюда. Сам объект также должен быть освобождён здесь. Вот пример этой функции:16741675```c1676static void1677newdatatype_dealloc(newdatatypeobject * obj)1678{1679    free(obj->obj_UnderlyingDatatypePtr);1680    Py_TYPE(obj)->tp_free(obj);1681}1682```16831684Важное требование к функции деаллокатора – она не должна затрагивать ожидающие исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при нормальных возвратах), не предпринимается никаких действий для защиты деаллокаторов от того, что исключение уже установлено. Любые действия, выполняемые деаллокатором, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к ложным ошибкам интерпретатора. Правильный способ защиты – сохранить ожидающее исключение перед выполнением опасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/3.1/c-api/exceptions.html#PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/3.1/c-api/exceptions.html#PyErr_Restore):16851686```c1687static void1688my_dealloc(PyObject *obj)1689{1690    MyObject *self = (MyObject *) obj;1691    PyObject *cbresult;16921693    if (self->my_callback != NULL) {1694        PyObject *err_type, *err_value, *err_traceback;1695        int have_error = PyErr_Occurred() ? 1 : 0;16961697        if (have_error)1698            PyErr_Fetch(&err_type, &err_value, &err_traceback);16991700        cbresult = PyObject_CallObject(self->my_callback, NULL);1701        if (cbresult == NULL)1702            PyErr_WriteUnraisable(self->my_callback);1703        else1704            Py_DECREF(cbresult);17051706        if (have_error)1707            PyErr_Restore(err_type, err_value, err_traceback);17081709        Py_DECREF(self->my_callback);1710    }1711    Py_TYPE(obj)->tp_free((PyObject*)self);1712}1713```17141715### 2.2.2. Представление объекта17161717В Python есть два способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.1/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.1/library/functions.html#str). (Функция [`print()`](https://python-all.ru/3.1/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.1/library/functions.html#str).) Оба обработчика необязательны.17181719```c1720reprfunc tp_repr;1721reprfunc tp_str;1722```17231724Обработчик [`tp_repr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызывается. Вот простой пример:17251726```c1727static PyObject *1728newdatatype_repr(newdatatypeobject * obj)1729{1730    return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1731                               obj->obj_UnderlyingDatatypePtr->size);1732}1733```17341735Если обработчик [`tp_repr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_repr) не указан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.1/c-api/typeobj.html#tp_name) типа и уникальный идентификатор объекта.17361737Обработчик [`tp_str`](https://python-all.ru/3.1/c-api/typeobj.html#tp_str) для [`str()`](https://python-all.ru/3.1/library/functions.html#str) – это то же самое, что описанный выше обработчик [`tp_repr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_repr) для [`repr()`](https://python-all.ru/3.1/library/functions.html#repr); то есть он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.1/library/functions.html#str) на экземпляре вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_repr), но результирующая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.1/c-api/typeobj.html#tp_str) не указан, вместо него используется обработчик [`tp_repr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_repr).17381739Вот простой пример:17401741```c1742static PyObject *1743newdatatype_str(newdatatypeobject * obj)1744{1745    return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",1746                               obj->obj_UnderlyingDatatypePtr->size);1747}1748```17491750### 2.2.3. Управление атрибутами17511752Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.17531754Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, достаточно реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.1/c-api/structures.html#PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.17551756```c1757getattrfunc  tp_getattr;        /* char * version */1758setattrfunc  tp_setattr;1759/* ... */1760getattrofunc tp_getattro;       /* PyObject * version */1761setattrofunc tp_setattro;1762```17631764Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют обобщённые реализации, которые можно использовать для предоставления версии функций управления атрибутами с [`PyObject*`](https://python-all.ru/3.1/c-api/structures.html#PyObject). Фактическая потребность в специфичных для типа обработчиках атрибутов почти полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового обобщённого механизма.17651766#### 2.2.3.1. Общее управление атрибутами17671768Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:176917701. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready).17712. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17721773Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.17741775When [`PyType_Ready()`](https://python-all.ru/3.1/c-api/type.html#PyType_Ready) is called, it uses three tables referenced by the type object to create [*descriptor*](https://python-all.ru/3.1/glossary.html#term-descriptor)s which are placed in the dictionary of the type object. Each descriptor controls access to one attribute of the instance object. Each of the tables is optional; if all three are *NULL*, instances of the type will only have attributes that are inherited from their base type, and should leave the [`tp_getattro`](https://python-all.ru/3.1/c-api/typeobj.html#tp_getattro) and [`tp_setattro`](https://python-all.ru/3.1/c-api/typeobj.html#tp_setattro) fields *NULL* as well, allowing the base type to handle attributes.17761777Таблицы объявлены как три поля объекта типа:17781779```c1780struct PyMethodDef *tp_methods;1781struct PyMemberDef *tp_members;1782struct PyGetSetDef *tp_getset;1783```17841785Если [`tp_methods`](https://python-all.ru/3.1/c-api/typeobj.html#tp_methods) не равен *NULL*, он должен указывать на массив структур [`PyMethodDef`](https://python-all.ru/3.1/c-api/structures.html#PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:17861787```c1788typedef struct PyMethodDef {1789    char        *ml_name;       /* имя метода */1790    PyCFunction  ml_meth;       /* функция реализации */1791    int          ml_flags;      /* флаги */1792    char        *ml_doc;        /* докстринг */1793} PyMethodDef;1794```17951796Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется ещё одна запись – это сигнальный элемент, обозначающий конец массива. Поле `ml_name` сигнального элемента должно быть равно *NULL*.17971798XXX Нужно сослаться на какое-то единое обсуждение полей структуры, общее со следующим разделом.17991800Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:18011802```c1803typedef struct PyMemberDef {1804    char *name;1805    int   type;1806    int   offset;1807    int   flags;1808    char *doc;1809} PyMemberDef;1810```18111812Для каждой записи в таблице будет создан [*дескриптор*](https://python-all.ru/3.1/glossary.html#term-descriptor) и добавлен к типу; он сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.1/library/functions.html#type) должно содержать один из кодов типа, определённых в заголовочном файле `structmember.h`; это значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.18131814XXX Нужно перенести часть этого в общий раздел!18151816Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.18171818| Константа | Значение |1819| --- | --- |1820| `READONLY` | Никогда не доступен для записи. |1821| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1822| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1823| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |18241825Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.1/c-api/typeobj.html#tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.18261827Как и в таблице [`tp_methods`](https://python-all.ru/3.1/c-api/typeobj.html#tp_methods), требуется сигнальный элемент со значением `name` равным *NULL*.18281829#### 2.2.3.2. Управление атрибутами, специфичное для типа18301831Для простоты здесь будет продемонстрирована только версия с `char*`; разница между версиями интерфейса с `char*` и [`PyObject*`](https://python-all.ru/3.1/c-api/structures.html#PyObject) заключается только в типе параметра name. Этот пример по сути делает то же самое, что и обобщённый пример выше, но не использует обобщённую поддержку, добавленную в Python 2.2. Он объясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, было понятно, что нужно делать.18321833Обработчик [`tp_getattr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, в которых вызывался бы метод [`__getattr__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattr__) класса.18341835Вот пример:18361837```c1838static PyObject *1839newdatatype_getattr(newdatatypeobject *obj, char *name)1840{1841    if (strcmp(name, "data") == 0)1842    {1843        return PyInt_FromLong(obj->data);1844    }18451846    PyErr_Format(PyExc_AttributeError,1847                 "'%.50s' object has no attribute '%.400s'",1848                 tp->tp_name, name);1849    return NULL;1850}1851```18521853Обработчик [`tp_setattr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_setattr) вызывается при вызове метода [`__setattr__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__delattr__) экземпляра класса. Когда атрибут должен быть удалён, третий параметр будет равен *NULL*. Вот пример, который просто возбуждает исключение; если бы это действительно было всё, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.1/c-api/typeobj.html#tp_setattr) должен быть установлен в *NULL*.18541855```c1856static int1857newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1858{1859    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1860    return -1;1861}1862```18631864### 2.2.4. Сравнение объектов18651866```c1867richcmpfunc tp_richcompare;1868```18691870Обработчик [`tp_richcompare`](https://python-all.ru/3.1/c-api/typeobj.html#tp_richcompare) вызывается, когда требуются сравнения. Он аналогичен [*методам расширенного сравнения*](https://python-all.ru/3.1/reference/datamodel.html#richcmpfuncs), таким как [`__lt__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__lt__), и также вызывается функциями [`PyObject_RichCompare()`](https://python-all.ru/3.1/c-api/object.html#PyObject_RichCompare) и [`PyObject_RichCompareBool()`](https://python-all.ru/3.1/c-api/object.html#PyObject_RichCompareBool).18711872Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор – это один из `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GT`, `Py_LT` или `Py_GT`. Она должна сравнить два объекта в соответствии с указанным оператором и вернуть `Py_True` или `Py_False`, если сравнение успешно, `Py_NotImplemented`, чтобы указать, что сравнение не реализовано и следует попробовать метод сравнения другого объекта, или *NULL*, если было установлено исключение.18731874Вот пример реализации для типа данных, который считается равным, если размер внутреннего указателя одинаков:18751876```c1877static int1878newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)1879{1880    PyObject *result;1881    int c, size1, size2;18821883    /* код для проверки, что оба аргумента имеют нужный тип1884       newdatatype опущен */18851886    size1 = obj1->obj_UnderlyingDatatypePtr->size;1887    size2 = obj2->obj_UnderlyingDatatypePtr->size;18881889    switch (op) {1890    case Py_LT: c = size1 <  size2; break;1891    case Py_LE: c = size1 <= size2; break;1892    case Py_EQ: c = size1 == size2; break;1893    case Py_NE: c = size1 != size2; break;1894    case Py_GT: c = size1 >  size2; break;1895    case Py_GE: c = size1 >= size2; break;1896    }1897    result = c ? Py_True : Py_False;1898    Py_INCREF(result);1899    return result;1900 }1901```19021903### 2.2.5. Поддержка абстрактных протоколов19041905Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [*Уровень абстрактных объектов*](https://python-all.ru/3.1/c-api/abstract.html#abstract).19061907Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)19081909```c1910PyNumberMethods   tp_as_number;1911PySequenceMethods tp_as_sequence;1912PyMappingMethods  tp_as_mapping;1913```19141915Если нужно, чтобы объект мог вести себя как число, последовательность или отображение, то следует поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.1/c-api/typeobj.html#PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.1/c-api/typeobj.html#PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.1/c-api/typeobj.html#PyMappingMethods) соответственно. Заполнить эту структуру подходящими значениями нужно самостоятельно. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.19161917```c1918hashfunc tp_hash;1919```19201921Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:19221923```c1924static long1925newdatatype_hash(newdatatypeobject *obj)1926{1927    long result;1928    result = obj->obj_UnderlyingDatatypePtr->size;1929    result = result * 3;1930    return result;1931}1932```19331934```c1935ternaryfunc tp_call;1936```19371938Эта функция вызывается, когда экземпляр вашего типа данных «вызывается». Например, если `obj1` – экземпляр вашего типа данных, а скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.1/c-api/typeobj.html#tp_call).19391940Эта функция принимает три аргумента:194119421. *arg1* – это экземпляр типа данных, который является субъектом вызова. Если вызовом является `obj1('hello')`, то *arg1* – это `obj1`.19432. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/3.1/c-api/arg.html#PyArg_ParseTuple).19443. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/3.1/c-api/arg.html#PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите [`TypeError`](https://python-all.ru/3.1/library/exceptions.html#TypeError) с сообщением о том, что именованные аргументы не поддерживаются.19451946Вот бессистемный пример реализации функции call.19471948```c1949/* Реализовать функцию вызова.1950 *    obj1 – экземпляр, принимающий вызов.1951 *    obj2 – кортеж, содержащий аргументы вызова, в данном1952 *         случае 3 строки.1953 */1954static PyObject *1955newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1956{1957    PyObject *result;1958    char *arg1;1959    char *arg2;1960    char *arg3;19611962    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1963        return NULL;1964    }1965    result = PyString_FromFormat(1966        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1967        obj->obj_UnderlyingDatatypePtr->size,1968        arg1, arg2, arg3);1969    printf("\%s", PyString_AS_STRING(result));1970    return result;1971}1972```19731974XXX некоторые поля нужно добавить сюда...19751976```c1977/* Итераторы */1978getiterfunc tp_iter;1979iternextfunc tp_iternext;1980```19811982Эти функции обеспечивают поддержку протокола итератора. Любой объект, который должен поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика: `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.19831984Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), следует создавать и возвращать новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, а также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.19851986Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только один раз). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.1/library/exceptions.html#StopIteration); пропуск исключения может дать несколько лучшую производительность. Если произошла реальная ошибка, он должен установить исключение и вернуть *NULL*.19871988### 2.2.6. Поддержка слабых ссылок19891990Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).19911992Чтобы объект можно было использовать в слабых ссылках, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.1/c-api/structures.html#PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.1/c-api/typeobj.html#tp_weaklistoffset) соответствующего объекта типа в смещение этого поля. Например, тип экземпляра определён следующей структурой:19931994```c1995typedef struct {1996    PyObject_HEAD1997    PyClassObject *in_class;       /* Объект класса */1998    PyObject      *in_dict;        /* Словарь */1999    PyObject      *in_weakreflist; /* Список слабых ссылок */2000} PyInstanceObject;2001```20022003Статически объявленный объект типа для экземпляров определяется следующим образом:20042005```c2006PyTypeObject PyInstance_Type = {2007    PyVarObject_HEAD_INIT(&PyType_Type, 0)2008    0,2009    "module.instance",20102011    /* Многое опущено для краткости... */20122013    Py_TPFLAGS_DEFAULT,                         /* tp_flags */2014    0,                                          /* tp_doc */2015    0,                                          /* tp_traverse */2016    0,                                          /* tp_clear */2017    0,                                          /* tp_richcompare */2018    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */2019};2020```20212022Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:20232024```c2025static PyObject *2026instance_new() {2027    /* Прочие детали инициализации опущены для краткости */20282029    self->in_weakreflist = NULL;20302031    return (PyObject *) self;2032}2033```20342035Единственное дополнение состоит в том, что деструктор должен вызвать менеджер слабых ссылок, чтобы очистить любые слабые ссылки. Это должно быть сделано до того, как произойдет любая другая часть разрушения, но требуется только в том случае, если список слабых ссылок не равен *NULL*:20362037```c2038static void2039instance_dealloc(PyInstanceObject *inst)2040{2041    /* Выделить временные объекты, если нужно, но не начинать2042       уничтожение пока не начинать2043     */20442045    if (inst->in_weakreflist != NULL)2046        PyObject_ClearWeakRefs((PyObject *) inst);20472048    /* Продолжить уничтожение объекта обычным образом. */2049}2050```20512052### 2.2.7. Дополнительные рекомендации20532054Помните, что большинство этих функций можно опустить; в этом случае нужно указать `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с дистрибутивом исходного кода Python.20552056Чтобы узнать, как реализовать какой-либо конкретный метод для вашего нового типа данных, сделайте следующее: скачайте и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем найдите в файлах C `tp_` плюс нужную функцию (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.20572058Когда нужно проверить, что объект является экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/3.1/c-api/object.html#PyObject_TypeCheck). Пример её использования может выглядеть следующим образом:20592060```c2061if (! PyObject_TypeCheck(some_object, &MyType)) {2062    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");2063    return NULL;2064}2065```20662067Сноски20682069| [\[1\]](https://python-all.ru/3.1/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2070| --- | --- |20712072| [\[2\]](https://python-all.ru/3.1/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.1/c-api/typeobj.html#tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы, которые позволяют «открепить» объект от сборки мусора, однако эти вызовы являются продвинутыми и не рассматриваются здесь. |2073| --- | --- |20742075| [\[3\]](https://python-all.ru/3.1/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2076| --- | --- |20772078| [\[4\]](https://python-all.ru/3.1/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2079| --- | --- |2080