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

newtypes.md

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

1> **Источник:** https://python-all.ru/3.4/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.4/c-api/structures.html#c.PyObject), который служит «базовым типом» для всех объектов Python. Сам [`PyObject`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject) содержит только счётчик ссылок и указатель на «объект типа» данного объекта. Именно здесь происходит основная работа: объект типа определяет, какие (C) функции вызываются, например, когда у объекта запрашивается атрибут или он умножается на другой объект. Эти C-функции называются «методами типа».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.4/c-api/type.html#c.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.4/c-api/type.html#c.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.4/c-api/allocation.html#c.PyObject_New).165166> **Примечание**167>168> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и при этом у вашего типа такое же значение [`tp_basicsize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), как у его базового типа, могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа в Python должен будет указывать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.4/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__) вашего типа без ошибки. Эту проблему можно избежать, если гарантировать, что значение [`tp_basicsize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа больше, чем у его базового типа. В большинстве случаев это и так верно, потому что либо ваш базовый тип – [`object`](https://python-all.ru/3.4/library/functions.html#object), либо вы добавляете поля данных к базовому типу и тем самым увеличиваете его размер.169170```c1710,                          /* tp_itemsize */172```173174Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.175176Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.4/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).177178```c179Py_TPFLAGS_DEFAULT,        /* tp_flags */180```181182Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.183184Строку документации для типа мы указываем в [`tp_doc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_doc).185186```c187"Noddy objects",           /* tp_doc */188```189190Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.191192Пока что всё, что нам нужно уметь делать – создавать новые объекты `Noddy`. Чтобы разрешить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new). В данном случае можно просто использовать стандартную реализацию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew). Хотелось бы просто присвоить её слоту [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new), но из соображений переносимости мы не можем этого сделать. На некоторых платформах или компиляторах нельзя статически инициализировать член структуры функцией, определённой в другом C-модуле, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.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`, который мы изначально установили в *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            Py_DECREF(self);275            return NULL;276        }277278        self->last = PyUnicode_FromString("");279        if (self->last == NULL) {280            Py_DECREF(self);281            return NULL;282        }283284        self->number = 0;285    }286287    return (PyObject *)self;288}289290static int291Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)292{293    PyObject *first=NULL, *last=NULL, *tmp;294295    static char *kwlist[] = {"first", "last", "number", NULL};296297    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,298                                      &first, &last,299                                      &self->number))300        return -1;301302    if (first) {303        tmp = self->first;304        Py_INCREF(first);305        self->first = first;306        Py_XDECREF(tmp);307    }308309    if (last) {310        tmp = self->last;311        Py_INCREF(last);312        self->last = last;313        Py_XDECREF(tmp);314    }315316    return 0;317}318319static PyMemberDef Noddy_members[] = {320    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,321     "first name"},322    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,323     "last name"},324    {"number", T_INT, offsetof(Noddy, number), 0,325     "noddy number"},326    {NULL}  /* Страж */327};328329static PyObject *330Noddy_name(Noddy* self)331{332    if (self->first == NULL) {333        PyErr_SetString(PyExc_AttributeError, "first");334        return NULL;335    }336337    if (self->last == NULL) {338        PyErr_SetString(PyExc_AttributeError, "last");339        return NULL;340    }341342    return PyUnicode_FromFormat("%S %S", self->first, self->last);343}344345static PyMethodDef Noddy_methods[] = {346    {"name", (PyCFunction)Noddy_name, METH_NOARGS,347     "Return the name, combining the first and last name"348    },349    {NULL}  /* Страж */350};351352static PyTypeObject NoddyType = {353    PyVarObject_HEAD_INIT(NULL, 0)354    "noddy.Noddy",             /* tp_name */355    sizeof(Noddy),             /* tp_basicsize */356    0,                         /* tp_itemsize */357    (destructor)Noddy_dealloc, /* tp_dealloc */358    0,                         /* tp_print */359    0,                         /* tp_getattr */360    0,                         /* tp_setattr */361    0,                         /* tp_reserved */362    0,                         /* tp_repr */363    0,                         /* tp_as_number */364    0,                         /* tp_as_sequence */365    0,                         /* tp_as_mapping */366    0,                         /* tp_hash  */367    0,                         /* tp_call */368    0,                         /* tp_str */369    0,                         /* tp_getattro */370    0,                         /* tp_setattro */371    0,                         /* tp_as_buffer */372    Py_TPFLAGS_DEFAULT |373        Py_TPFLAGS_BASETYPE,   /* tp_flags */374    "Noddy objects",           /* tp_doc */375    0,                         /* tp_traverse */376    0,                         /* tp_clear */377    0,                         /* tp_richcompare */378    0,                         /* tp_weaklistoffset */379    0,                         /* tp_iter */380    0,                         /* tp_iternext */381    Noddy_methods,             /* tp_methods */382    Noddy_members,             /* tp_members */383    0,                         /* tp_getset */384    0,                         /* tp_base */385    0,                         /* tp_dict */386    0,                         /* tp_descr_get */387    0,                         /* tp_descr_set */388    0,                         /* tp_dictoffset */389    (initproc)Noddy_init,      /* tp_init */390    0,                         /* tp_alloc */391    Noddy_new,                 /* tp_new */392};393394static PyModuleDef noddy2module = {395    PyModuleDef_HEAD_INIT,396    "noddy2",397    "Example module that creates an extension type.",398    -1,399    NULL, NULL, NULL, NULL, NULL400};401402PyMODINIT_FUNC403PyInit_noddy2(void)404{405    PyObject* m;406407    if (PyType_Ready(&NoddyType) < 0)408        return NULL;409410    m = PyModule_Create(&noddy2module);411    if (m == NULL)412        return NULL;413414    Py_INCREF(&NoddyType);415    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);416    return m;417}418```419420Эта версия модуля содержит ряд изменений.421422Мы добавили дополнительный include:423424```c425#include <structmember.h>426```427428Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.429430Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа сокращено до `NoddyType`.431432Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* – это строки Python, содержащие имя и фамилию. Атрибут *number* – целое число.433434Структура объекта обновляется соответствующим образом:435436```c437typedef struct {438    PyObject_HEAD439    PyObject *first;440    PyObject *last;441    int number;442} Noddy;443```444445Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:446447```c448static void449Noddy_dealloc(Noddy* self)450{451    Py_XDECREF(self->first);452    Py_XDECREF(self->last);453    Py_TYPE(self)->tp_free((PyObject*)self);454}455```456457которое присваивается члену [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):458459```c460(destructor)Noddy_dealloc, /*tp_dealloc*/461```462463Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, потому что объект может быть экземпляром подкласса.464465Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:466467```c468static PyObject *469Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)470{471    Noddy *self;472473    self = (Noddy *)type->tp_alloc(type, 0);474    if (self != NULL) {475        self->first = PyUnicode_FromString("");476        if (self->first == NULL) {477            Py_DECREF(self);478            return NULL;479        }480481        self->last = PyUnicode_FromString("");482        if (self->last == NULL) {483            Py_DECREF(self);484            return NULL;485        }486487        self->number = 0;488    }489490    return (PyObject *)self;491}492```493494и устанавливаем его в член [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new):495496```c497Noddy_new,                 /* tp_new */498```499500Член new отвечает за создание (в отличие от инициализации) объектов типа. В Python он доступен как метод [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__). Подробное обсуждение метода [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__) приведено в статье «Unifying types and classes in Python». Одна из причин реализовать метод new – гарантировать начальные значения переменных экземпляра. В данном случае мы используем метод new, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew) в качестве метода new, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.501502Метод new – это статический метод, который получает тип, создаваемый в данный момент, и любые аргументы, переданные при вызове типа, и возвращает новый созданный объект. Методы new всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам инициализации. Обратите внимание: если тип поддерживает наследование, переданный тип может не быть тем типом, который определяется. Метод new вызывает слот [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) заполняет его, наследуя от нашего базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.4/library/functions.html#object). Большинство типов использует выделение по умолчанию.503504> **Примечание**505>506> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) базового типа или [`__new__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__new__)), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не сделать, подклассы вашего типа в Python, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.4/library/exceptions.html#TypeError).)507508Мы предоставляем функцию инициализации:509510```c511static int512Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)513{514    PyObject *first=NULL, *last=NULL, *tmp;515516    static char *kwlist[] = {"first", "last", "number", NULL};517518    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,519                                      &first, &last,520                                      &self->number))521        return -1;522523    if (first) {524        tmp = self->first;525        Py_INCREF(first);526        self->first = first;527        Py_XDECREF(tmp);528    }529530    if (last) {531        tmp = self->last;532        Py_INCREF(last);533        self->last = last;534        Py_XDECREF(tmp);535    }536537    return 0;538}539```540541заполнив слот [`tp_init`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_init).542543```c544(initproc)Noddy_init,         /* tp_init */545```546547Слот [`tp_init`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, мы не можем гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке объектов и может быть переопределён. Наш инициализатор принимает аргументы для предоставления начальных значений экземпляра. Инициализаторы всегда принимают позиционные и ключевые аргументы. Инициализаторы должны возвращать 0 в случае успеха или -1 в случае ошибки.548549Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__init__) на наших объектах. Поэтому нужно быть особенно осторожными при присваивании новых значений. Например, можно было бы попытаться присвоить члену `first` значение так:550551```c552if (first) {553    Py_XDECREF(self->first);554    Py_INCREF(first);555    self->first = first;556}557```558559Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому он может быть объектом любого типа. У него может быть деструктор, который вызывает код, пытающийся обратиться к члену `first`. Чтобы быть предусмотрительными и защититься от такой возможности, мы почти всегда переприсваиваем члены перед уменьшением их счётчиков ссылок. Когда же этого делать не нужно?560561- когда точно известно, что счётчик ссылок больше 1562- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.4/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа563- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.4/extending/newtypes.html#id8)564565Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:566567```c568static PyMemberDef Noddy_members[] = {569    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,570     "first name"},571    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,572     "last name"},573    {"number", T_INT, offsetof(Noddy, number), 0,574     "noddy number"},575    {NULL}  /* Страж */576};577```578579и поместите определения в слот [`tp_members`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_members):580581```c582Noddy_members,             /* tp_members */583```584585Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [*Управление атрибутами общего назначения*](https://python-all.ru/3.4/extending/newtypes.html#generic-attribute-management) ниже.586587Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.588589Определяем один метод `name()`, который выводит имя объекта как объединение имени и фамилии.590591```c592static PyObject *593Noddy_name(Noddy* self)594{595    if (self->first == NULL) {596        PyErr_SetString(PyExc_AttributeError, "first");597        return NULL;598    }599600    if (self->last == NULL) {601        PyErr_SetString(PyExc_AttributeError, "last");602        return NULL;603    }604605    return PyUnicode_FromFormat("%S %S", self->first, self->last);606}607```608609Метод реализован как функция на C, которая принимает экземпляр `Noddy` (или подкласса `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в принятии кортежа позиционных аргументов или словаря именованных аргументов. Этот метод эквивалентен методу Python:610611```c612def name(self):613   return "%s %s" % (self.first, self.last)614```615616Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше предотвратить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать, в следующем разделе.617618Теперь, когда мы определили метод, нужно создать массив определений методов:619620```c621static PyMethodDef Noddy_methods[] = {622    {"name", (PyCFunction)Noddy_name, METH_NOARGS,623     "Return the name, combining the first and last name"624    },625    {NULL}  /* Страж */626};627```628629и назначаем их в слот [`tp_methods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_methods):630631```c632Noddy_methods,             /* tp_methods */633```634635Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.4/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаются аргументы.636637Наконец, сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы осторожно, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.4/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флагов нашего класса:638639```c640Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/641```642643Переименовываем `PyInit_noddy()` в `PyInit_noddy2()` и обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.4/c-api/module.html#c.PyModuleDef).644645Наконец, обновляем наш файл `setup.py` для сборки нового модуля:646647```c648from distutils.core import setup, Extension649setup(name="noddy", version="1.0",650      ext_modules=[651         Extension("noddy", ["noddy.c"]),652         Extension("noddy2", ["noddy2.c"]),653         ])654```655656### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных657658В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.659660```c661#include <Python.h>662#include "structmember.h"663664typedef struct {665    PyObject_HEAD666    PyObject *first;667    PyObject *last;668    int number;669} Noddy;670671static void672Noddy_dealloc(Noddy* self)673{674    Py_XDECREF(self->first);675    Py_XDECREF(self->last);676    Py_TYPE(self)->tp_free((PyObject*)self);677}678679static PyObject *680Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)681{682    Noddy *self;683684    self = (Noddy *)type->tp_alloc(type, 0);685    if (self != NULL) {686        self->first = PyUnicode_FromString("");687        if (self->first == NULL) {688            Py_DECREF(self);689            return NULL;690        }691692        self->last = PyUnicode_FromString("");693        if (self->last == NULL) {694            Py_DECREF(self);695            return NULL;696        }697698        self->number = 0;699    }700701    return (PyObject *)self;702}703704static int705Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)706{707    PyObject *first=NULL, *last=NULL, *tmp;708709    static char *kwlist[] = {"first", "last", "number", NULL};710711    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,712                                      &first, &last,713                                      &self->number))714        return -1;715716    if (first) {717        tmp = self->first;718        Py_INCREF(first);719        self->first = first;720        Py_DECREF(tmp);721    }722723    if (last) {724        tmp = self->last;725        Py_INCREF(last);726        self->last = last;727        Py_DECREF(tmp);728    }729730    return 0;731}732733static PyMemberDef Noddy_members[] = {734    {"number", T_INT, offsetof(Noddy, number), 0,735     "noddy number"},736    {NULL}  /* Страж */737};738739static PyObject *740Noddy_getfirst(Noddy *self, void *closure)741{742    Py_INCREF(self->first);743    return self->first;744}745746static int747Noddy_setfirst(Noddy *self, PyObject *value, void *closure)748{749    if (value == NULL) {750        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");751        return -1;752    }753754    if (! PyUnicode_Check(value)) {755        PyErr_SetString(PyExc_TypeError,756                        "The first attribute value must be a string");757        return -1;758    }759760    Py_DECREF(self->first);761    Py_INCREF(value);762    self->first = value;763764    return 0;765}766767static PyObject *768Noddy_getlast(Noddy *self, void *closure)769{770    Py_INCREF(self->last);771    return self->last;772}773774static int775Noddy_setlast(Noddy *self, PyObject *value, void *closure)776{777    if (value == NULL) {778        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");779        return -1;780    }781782    if (! PyUnicode_Check(value)) {783        PyErr_SetString(PyExc_TypeError,784                        "The last attribute value must be a string");785        return -1;786    }787788    Py_DECREF(self->last);789    Py_INCREF(value);790    self->last = value;791792    return 0;793}794795static PyGetSetDef Noddy_getseters[] = {796    {"first",797     (getter)Noddy_getfirst, (setter)Noddy_setfirst,798     "first name",799     NULL},800    {"last",801     (getter)Noddy_getlast, (setter)Noddy_setlast,802     "last name",803     NULL},804    {NULL}  /* Страж */805};806807static PyObject *808Noddy_name(Noddy* self)809{810    return PyUnicode_FromFormat("%S %S", self->first, self->last);811}812813static PyMethodDef Noddy_methods[] = {814    {"name", (PyCFunction)Noddy_name, METH_NOARGS,815     "Return the name, combining the first and last name"816    },817    {NULL}  /* Страж */818};819820static PyTypeObject NoddyType = {821    PyVarObject_HEAD_INIT(NULL, 0)822    "noddy.Noddy",             /* tp_name */823    sizeof(Noddy),             /* tp_basicsize */824    0,                         /* tp_itemsize */825    (destructor)Noddy_dealloc, /* tp_dealloc */826    0,                         /* tp_print */827    0,                         /* tp_getattr */828    0,                         /* tp_setattr */829    0,                         /* tp_reserved */830    0,                         /* tp_repr */831    0,                         /* tp_as_number */832    0,                         /* tp_as_sequence */833    0,                         /* tp_as_mapping */834    0,                         /* tp_hash  */835    0,                         /* tp_call */836    0,                         /* tp_str */837    0,                         /* tp_getattro */838    0,                         /* tp_setattro */839    0,                         /* tp_as_buffer */840    Py_TPFLAGS_DEFAULT |841        Py_TPFLAGS_BASETYPE,   /* tp_flags */842    "Noddy objects",           /* tp_doc */843    0,                         /* tp_traverse */844    0,                         /* tp_clear */845    0,                         /* tp_richcompare */846    0,                         /* tp_weaklistoffset */847    0,                         /* tp_iter */848    0,                         /* tp_iternext */849    Noddy_methods,             /* tp_methods */850    Noddy_members,             /* tp_members */851    Noddy_getseters,           /* tp_getset */852    0,                         /* tp_base */853    0,                         /* tp_dict */854    0,                         /* tp_descr_get */855    0,                         /* tp_descr_set */856    0,                         /* tp_dictoffset */857    (initproc)Noddy_init,      /* tp_init */858    0,                         /* tp_alloc */859    Noddy_new,                 /* tp_new */860};861862static PyModuleDef noddy3module = {863    PyModuleDef_HEAD_INIT,864    "noddy3",865    "Example module that creates an extension type.",866    -1,867    NULL, NULL, NULL, NULL, NULL868};869870PyMODINIT_FUNC871PyInit_noddy3(void)872{873    PyObject* m;874875    if (PyType_Ready(&NoddyType) < 0)876        return NULL;877878    m = PyModule_Create(&noddy3module);879    if (m == NULL)880        return NULL;881882    Py_INCREF(&NoddyType);883    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);884    return m;885}886```887888Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:889890```c891Noddy_getfirst(Noddy *self, void *closure)892{893    Py_INCREF(self->first);894    return self->first;895}896897static int898Noddy_setfirst(Noddy *self, PyObject *value, void *closure)899{900  if (value == NULL) {901    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");902    return -1;903  }904905  if (! PyUnicode_Check(value)) {906    PyErr_SetString(PyExc_TypeError,907                    "The first attribute value must be a str");908    return -1;909  }910911  Py_DECREF(self->first);912  Py_INCREF(value);913  self->first = value;914915  return 0;916}917```918919Функции получения передаётся объект `Noddy` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это, например, можно использовать, чтобы позволить единому набору функций получения и установки решать, какой атрибут получать или устанавливать, на основе данных в замыкании.)920921Функции установки передаётся объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.922923Создаём массив структур `PyGetSetDef`:924925```c926static PyGetSetDef Noddy_getseters[] = {927    {"first",928     (getter)Noddy_getfirst, (setter)Noddy_setfirst,929     "first name",930     NULL},931    {"last",932     (getter)Noddy_getlast, (setter)Noddy_setlast,933     "last name",934     NULL},935    {NULL}  /* Страж */936};937```938939и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_getset):940941```c942Noddy_getseters,           /* tp_getset */943```944945для регистрации геттеров и сеттеров наших атрибутов.946947Последний элемент структуры `PyGetSetDef` – это упомянутое выше замыкание. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.948949Также удаляем определения членов для этих атрибутов:950951```c952static PyMemberDef Noddy_members[] = {953    {"number", T_INT, offsetof(Noddy, number), 0,954     "noddy number"},955    {NULL}  /* Страж */956};957```958959Также необходимо обновить обработчик [`tp_init`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.4/extending/newtypes.html#id9):960961```c962static int963Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)964{965    PyObject *first=NULL, *last=NULL, *tmp;966967    static char *kwlist[] = {"first", "last", "number", NULL};968969    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,970                                      &first, &last,971                                      &self->number))972        return -1;973974    if (first) {975        tmp = self->first;976        Py_INCREF(first);977        self->first = first;978        Py_DECREF(tmp);979    }980981    if (last) {982        tmp = self->last;983        Py_INCREF(last);984        self->last = last;985        Py_DECREF(tmp);986    }987988    return 0;989}990```991992Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значения *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность того, что инициализация этих членов в конструкторе не удалась.993994Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как мы делали ранее, и добавляем дополнительное определение в файл `setup.py`.995996### 2.1.3. Поддержка циклической сборки мусора997998В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:9991000```c1001>>> l = []1002>>> l.append(l)1003>>> del l1004```10051006В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10071008Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.4/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:10091010```c1011>>> import noddy21012>>> n = noddy2.Noddy()1013>>> l = [n]1014>>> n.first = l1015```10161017Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типам необходимо заполнить два слота и установить флаг класса, включающий эти слоты:10181019```c1020#include <Python.h>1021#include "structmember.h"10221023typedef struct {1024    PyObject_HEAD1025    PyObject *first;1026    PyObject *last;1027    int number;1028} Noddy;10291030static int1031Noddy_traverse(Noddy *self, visitproc visit, void *arg)1032{1033    int vret;10341035    if (self->first) {1036        vret = visit(self->first, arg);1037        if (vret != 0)1038            return vret;1039    }1040    if (self->last) {1041        vret = visit(self->last, arg);1042        if (vret != 0)1043            return vret;1044    }10451046    return 0;1047}10481049static int1050Noddy_clear(Noddy *self)1051{1052    PyObject *tmp;10531054    tmp = self->first;1055    self->first = NULL;1056    Py_XDECREF(tmp);10571058    tmp = self->last;1059    self->last = NULL;1060    Py_XDECREF(tmp);10611062    return 0;1063}10641065static void1066Noddy_dealloc(Noddy* self)1067{1068    Noddy_clear(self);1069    Py_TYPE(self)->tp_free((PyObject*)self);1070}10711072static PyObject *1073Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1074{1075    Noddy *self;10761077    self = (Noddy *)type->tp_alloc(type, 0);1078    if (self != NULL) {1079        self->first = PyUnicode_FromString("");1080        if (self->first == NULL) {1081            Py_DECREF(self);1082            return NULL;1083        }10841085        self->last = PyUnicode_FromString("");1086        if (self->last == NULL) {1087            Py_DECREF(self);1088            return NULL;1089        }10901091        self->number = 0;1092    }10931094    return (PyObject *)self;1095}10961097static int1098Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1099{1100    PyObject *first=NULL, *last=NULL, *tmp;11011102    static char *kwlist[] = {"first", "last", "number", NULL};11031104    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,1105                                      &first, &last,1106                                      &self->number))1107        return -1;11081109    if (first) {1110        tmp = self->first;1111        Py_INCREF(first);1112        self->first = first;1113        Py_XDECREF(tmp);1114    }11151116    if (last) {1117        tmp = self->last;1118        Py_INCREF(last);1119        self->last = last;1120        Py_XDECREF(tmp);1121    }11221123    return 0;1124}11251126static PyMemberDef Noddy_members[] = {1127    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1128     "first name"},1129    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1130     "last name"},1131    {"number", T_INT, offsetof(Noddy, number), 0,1132     "noddy number"},1133    {NULL}  /* Страж */1134};11351136static PyObject *1137Noddy_name(Noddy* self)1138{1139    if (self->first == NULL) {1140        PyErr_SetString(PyExc_AttributeError, "first");1141        return NULL;1142    }11431144    if (self->last == NULL) {1145        PyErr_SetString(PyExc_AttributeError, "last");1146        return NULL;1147    }11481149    return PyUnicode_FromFormat("%S %S", self->first, self->last);1150}11511152static PyMethodDef Noddy_methods[] = {1153    {"name", (PyCFunction)Noddy_name, METH_NOARGS,1154     "Return the name, combining the first and last name"1155    },1156    {NULL}  /* Страж */1157};11581159static PyTypeObject NoddyType = {1160    PyVarObject_HEAD_INIT(NULL, 0)1161    "noddy.Noddy",             /* tp_name */1162    sizeof(Noddy),             /* tp_basicsize */1163    0,                         /* tp_itemsize */1164    (destructor)Noddy_dealloc, /* tp_dealloc */1165    0,                         /* tp_print */1166    0,                         /* tp_getattr */1167    0,                         /* tp_setattr */1168    0,                         /* tp_reserved */1169    0,                         /* tp_repr */1170    0,                         /* tp_as_number */1171    0,                         /* tp_as_sequence */1172    0,                         /* tp_as_mapping */1173    0,                         /* tp_hash  */1174    0,                         /* tp_call */1175    0,                         /* tp_str */1176    0,                         /* tp_getattro */1177    0,                         /* tp_setattro */1178    0,                         /* tp_as_buffer */1179    Py_TPFLAGS_DEFAULT |1180        Py_TPFLAGS_BASETYPE |1181        Py_TPFLAGS_HAVE_GC,    /* tp_flags */1182    "Noddy objects",           /* tp_doc */1183    (traverseproc)Noddy_traverse,   /* tp_traverse */1184    (inquiry)Noddy_clear,           /* tp_clear */1185    0,                         /* tp_richcompare */1186    0,                         /* tp_weaklistoffset */1187    0,                         /* tp_iter */1188    0,                         /* tp_iternext */1189    Noddy_methods,             /* tp_methods */1190    Noddy_members,             /* tp_members */1191    0,                         /* tp_getset */1192    0,                         /* tp_base */1193    0,                         /* tp_dict */1194    0,                         /* tp_descr_get */1195    0,                         /* tp_descr_set */1196    0,                         /* tp_dictoffset */1197    (initproc)Noddy_init,      /* tp_init */1198    0,                         /* tp_alloc */1199    Noddy_new,                 /* tp_new */1200};12011202static PyModuleDef noddy4module = {1203    PyModuleDef_HEAD_INIT,1204    "noddy4",1205    "Example module that creates an extension type.",1206    -1,1207    NULL, NULL, NULL, NULL, NULL1208};12091210PyMODINIT_FUNC1211PyInit_noddy4(void)1212{1213    PyObject* m;12141215    if (PyType_Ready(&NoddyType) < 0)1216        return NULL;12171218    m = PyModule_Create(&noddy4module);1219    if (m == NULL)1220        return NULL;12211222    Py_INCREF(&NoddyType);1223    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1224    return m;1225}1226```12271228Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:12291230```c1231static int1232Noddy_traverse(Noddy *self, visitproc visit, void *arg)1233{1234    int vret;12351236    if (self->first) {1237        vret = visit(self->first, arg);1238        if (vret != 0)1239            return vret;1240    }1241    if (self->last) {1242        vret = visit(self->last, arg);1243        if (vret != 0)1244            return vret;1245    }12461247    return 0;1248}1249```12501251Для каждого подобъекта, который может участвовать в циклах, необходимо вызвать функцию `visit()`, которая передаётся методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целочисленное значение, которое должно быть возвращено, если оно не равно нулю.12521253Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.4/c-api/gcsupport.html#c.Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.4/c-api/gcsupport.html#c.Py_VISIT) функцию `Noddy_traverse()` можно упростить:12541255```c1256static int1257Noddy_traverse(Noddy *self, visitproc visit, void *arg)1258{1259    Py_VISIT(self->first);1260    Py_VISIT(self->last);1261    return 0;1262}1263```12641265> **Примечание**1266>1267> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.4/c-api/gcsupport.html#c.Py_VISIT). Это сделано для поощрения единообразия в этих однообразных реализациях.12681269Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах. Реализуем этот метод и перереализуем деаллокатор для его использования:12701271```c1272static int1273Noddy_clear(Noddy *self)1274{1275    PyObject *tmp;12761277    tmp = self->first;1278    self->first = NULL;1279    Py_XDECREF(tmp);12801281    tmp = self->last;1282    self->last = NULL;1283    Py_XDECREF(tmp);12841285    return 0;1286}12871288static void1289Noddy_dealloc(Noddy* self)1290{1291    Noddy_clear(self);1292    Py_TYPE(self)->tp_free((PyObject*)self);1293}1294```12951296Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы установить каждый член в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это, потому что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратный вызов в объект. Кроме того, поскольку теперь мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, который запускает сборку мусора. Если сборка мусора запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_traverse). Мы не можем рисковать тем, что `Noddy_traverse()` будет вызвана, когда счётчик ссылок члена упал до нуля, а его значение ещё не установлено в *NULL*.12971298Python предоставляет макрос [`Py_CLEAR()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_CLEAR), автоматизирующий аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/3.4/c-api/refcounting.html#c.Py_CLEAR) функцию `Noddy_clear()` можно упростить:12991300```c1301static int1302Noddy_clear(Noddy *self)1303{1304    Py_CLEAR(self->first);1305    Py_CLEAR(self->last);1306    return 0;1307}1308```13091310Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.4/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13111312```c1313Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1314```13151316В общем, это всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_free), их нужно было бы изменить для циклической сборки мусора. Большинство расширений будут использовать автоматически предоставленные версии.13171318### 2.1.4. Наследование от других типов13191320Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужную ему структуру `PyTypeObject`. Совместное использование этих структур `PyTypeObject` между модулями расширений может быть сложным.13211322В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.4/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.13231324```c1325>>> import shoddy1326>>> s = shoddy.Shoddy(range(3))1327>>> s.extend(s)1328>>> print(len(s))132961330>>> print(s.increment())133111332>>> print(s.increment())133321334```13351336```c1337#include <Python.h>13381339typedef struct {1340    PyListObject list;1341    int state;1342} Shoddy;13431344static PyObject *1345Shoddy_increment(Shoddy *self, PyObject *unused)1346{1347    self->state++;1348    return PyLong_FromLong(self->state);1349}13501351static PyMethodDef Shoddy_methods[] = {1352    {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1353     PyDoc_STR("increment state counter")},1354    {NULL,	NULL},1355};13561357static int1358Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1359{1360    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1361        return -1;1362    self->state = 0;1363    return 0;1364}13651366static PyTypeObject ShoddyType = {1367    PyObject_HEAD_INIT(NULL)1368    "shoddy.Shoddy",         /* tp_name */1369    sizeof(Shoddy),          /* tp_basicsize */1370    0,                       /* tp_itemsize */1371    0,                       /* tp_dealloc */1372    0,                       /* tp_print */1373    0,                       /* tp_getattr */1374    0,                       /* tp_setattr */1375    0,                       /* tp_reserved */1376    0,                       /* tp_repr */1377    0,                       /* tp_as_number */1378    0,                       /* tp_as_sequence */1379    0,                       /* tp_as_mapping */1380    0,                       /* tp_hash */1381    0,                       /* tp_call */1382    0,                       /* tp_str */1383    0,                       /* tp_getattro */1384    0,                       /* tp_setattro */1385    0,                       /* tp_as_buffer */1386    Py_TPFLAGS_DEFAULT |1387        Py_TPFLAGS_BASETYPE, /* tp_flags */1388    0,                       /* tp_doc */1389    0,                       /* tp_traverse */1390    0,                       /* tp_clear */1391    0,                       /* tp_richcompare */1392    0,                       /* tp_weaklistoffset */1393    0,                       /* tp_iter */1394    0,                       /* tp_iternext */1395    Shoddy_methods,          /* tp_methods */1396    0,                       /* tp_members */1397    0,                       /* tp_getset */1398    0,                       /* tp_base */1399    0,                       /* tp_dict */1400    0,                       /* tp_descr_get */1401    0,                       /* tp_descr_set */1402    0,                       /* tp_dictoffset */1403    (initproc)Shoddy_init,   /* tp_init */1404    0,                       /* tp_alloc */1405    0,                       /* tp_new */1406};14071408static PyModuleDef shoddymodule = {1409    PyModuleDef_HEAD_INIT,1410    "shoddy",1411    "Shoddy module",1412    -1,1413    NULL, NULL, NULL, NULL, NULL1414};14151416PyMODINIT_FUNC1417PyInit_shoddy(void)1418{1419    PyObject *m;14201421    ShoddyType.tp_base = &PyList_Type;1422    if (PyType_Ready(&ShoddyType) < 0)1423        return NULL;14241425    m = PyModule_Create(&shoddymodule);1426    if (m == NULL)1427        return NULL;14281429    Py_INCREF(&ShoddyType);1430    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1431    return m;1432}1433```14341435Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.14361437```c1438typedef struct {1439    PyListObject list;1440    int state;1441} Shoddy;1442```14431444Основное различие для объектов производных типов заключается в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.14451446Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно приводить как к *PyListObject\**, так и к *Shoddy\**.14471448```c1449static int1450Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1451{1452    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1453       return -1;1454    self->state = 0;1455    return 0;1456}1457```14581459В методе `__init__` нашего типа видно, как вызвать метод `__init__` базового типа.14601461Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен непосредственно выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) – это будет сделано базовым классом при вызове его [`tp_new`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_new).14621463При заполнении структуры [`PyTypeObject()`](https://python-all.ru/3.4/c-api/type.html#c.PyTypeObject) для типа `Shoddy` можно увидеть слот для `tp_base()`. Из-за проблем с кроссплатформенными компиляторами нельзя напрямую заполнять это поле значением [`PyList_Type()`](https://python-all.ru/3.4/c-api/list.html#c.PyList_Type); это можно сделать позже в функции `init()` модуля.14641465```c1466PyMODINIT_FUNC1467PyInit_shoddy(void)1468{1469    PyObject *m;14701471    ShoddyType.tp_base = &PyList_Type;1472    if (PyType_Ready(&ShoddyType) < 0)1473        return NULL;14741475    m = PyModule_Create(&shoddymodule);1476    if (m == NULL)1477        return NULL;14781479    Py_INCREF(&ShoddyType);1480    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1481    return m;1482}1483```14841485Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) в структуре типа должен быть заполнен слот [`tp_base`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_base). При создании нового типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.14861487После этого вызов [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль ничем не отличаются от базовых примеров с `Noddy`.14881489## 2.2. Методы типа14901491Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.14921493Ниже приведено определение [`PyTypeObject`](https://python-all.ru/3.4/c-api/type.html#c.PyTypeObject), в котором опущены некоторые поля, используемые только в отладочных сборках:14941495```c1496typedef struct _typeobject {1497    PyObject_VAR_HEAD1498    const char *tp_name; /* Для вывода в формате "<module>.<name>" */1499    Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */15001501    /* Методы для реализации стандартных операций */15021503    destructor tp_dealloc;1504    printfunc tp_print;1505    getattrfunc tp_getattr;1506    setattrfunc tp_setattr;1507    void *tp_reserved; /* ранее известный как tp_compare */1508    reprfunc tp_repr;15091510    /* Наборы методов для стандартных классов */15111512    PyNumberMethods *tp_as_number;1513    PySequenceMethods *tp_as_sequence;1514    PyMappingMethods *tp_as_mapping;15151516    /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15171518    hashfunc tp_hash;1519    ternaryfunc tp_call;1520    reprfunc tp_str;1521    getattrofunc tp_getattro;1522    setattrofunc tp_setattro;15231524    /* Функции для доступа к объекту как к буферу ввода/вывода */1525    PyBufferProcs *tp_as_buffer;15261527    /* Флаги для определения наличия опциональных/расширенных возможностей */1528    unsigned long tp_flags;15291530    const char *tp_doc; /* Строка документации */15311532    /* вызов функции для всех доступных объектов */1533    traverseproc tp_traverse;15341535    /* удаление ссылок на содержащиеся объекты */1536    inquiry tp_clear;15371538    /* расширенные сравнения */1539    richcmpfunc tp_richcompare;15401541    /* включение слабых ссылок */1542    Py_ssize_t tp_weaklistoffset;15431544    /* Итераторы */1545    getiterfunc tp_iter;1546    iternextfunc tp_iternext;15471548    /* Дескриптор атрибутов и механизмы подклассов */1549    struct PyMethodDef *tp_methods;1550    struct PyMemberDef *tp_members;1551    struct PyGetSetDef *tp_getset;1552    struct _typeobject *tp_base;1553    PyObject *tp_dict;1554    descrgetfunc tp_descr_get;1555    descrsetfunc tp_descr_set;1556    Py_ssize_t tp_dictoffset;1557    initproc tp_init;1558    allocfunc tp_alloc;1559    newfunc tp_new;1560    freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1561    inquiry tp_is_gc; /* Для PyObject_IS_GC */1562    PyObject *tp_bases;1563    PyObject *tp_mro; /* порядок разрешения методов */1564    PyObject *tp_cache;1565    PyObject *tp_subclasses;1566    PyObject *tp_weaklist;1567    destructor tp_del;15681569    /* Метка версии кэша атрибутов типа. Добавлено в версии 2.6. */1570    unsigned int tp_version_tag;15711572    destructor tp_finalize;15731574} PyTypeObject;1575```15761577Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.15781579Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.15801581```c1582const char *tp_name; /* Для вывода */1583```15841585Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!15861587```c1588Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */1589```15901591Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов данного типа. В Python есть встроенная поддержка структур переменной длины (например, строки, списки), для которой предназначено поле [`tp_itemsize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_itemsize). Об этом будет рассказано позже.15921593```c1594const char *tp_doc;1595```15961597Сюда можно поместить строку (или её адрес), которая должна возвращаться, когда скрипт на Python обращается к `obj.__doc__` для получения строки документации.15981599Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16001601### 2.2.1. Финализация и освобождение памяти16021603```c1604destructor tp_dealloc;1605```16061607Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа уменьшается до нуля и интерпретатор Python хочет освободить его. Если ваш тип требует освобождения памяти или другой очистки, этот код можно разместить здесь. Сам объект также должен быть освобождён здесь. Вот пример такой функции:16081609```c1610static void1611newdatatype_dealloc(newdatatypeobject * obj)1612{1613    free(obj->obj_UnderlyingDatatypePtr);1614    Py_TYPE(obj)->tp_free(obj);1615}1616```16171618Важное требование к функции деаллокатора – она не должна затрагивать ожидающие исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при нормальных возвратах), не предпринимается никаких действий для защиты деаллокаторов от того, что исключение уже установлено. Любые действия, выполняемые деаллокатором, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к ложным ошибкам интерпретатора. Правильный способ защиты – сохранить ожидающее исключение перед выполнением опасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/3.4/c-api/exceptions.html#c.PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/3.4/c-api/exceptions.html#c.PyErr_Restore):16191620```c1621static void1622my_dealloc(PyObject *obj)1623{1624    MyObject *self = (MyObject *) obj;1625    PyObject *cbresult;16261627    if (self->my_callback != NULL) {1628        PyObject *err_type, *err_value, *err_traceback;16291630        /* Сохраняет текущее состояние исключения */1631        PyErr_Fetch(&err_type, &err_value, &err_traceback);16321633        cbresult = PyObject_CallObject(self->my_callback, NULL);1634        if (cbresult == NULL)1635            PyErr_WriteUnraisable(self->my_callback);1636        else1637            Py_DECREF(cbresult);16381639        /* Восстанавливает сохранённое состояние исключения */1640        PyErr_Restore(err_type, err_value, err_traceback);16411642        Py_DECREF(self->my_callback);1643    }1644    Py_TYPE(obj)->tp_free((PyObject*)self);1645}1646```16471648> **Примечание**1649>1650> Существуют ограничения на то, что можно безопасно делать в функции деаллокатора. Во-первых, если ваш тип поддерживает сборку мусора (с помощью [`tp_traverse`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_traverse) и/или [`tp_clear`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_clear)), некоторые члены объекта могли быть очищены или финализированы к моменту вызова [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Во-вторых, в [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) объект находится в нестабильном состоянии: его счётчик ссылок равен нулю. Любой вызов нетривиального объекта или API (как в примере выше) может снова вызвать [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), что приведёт к двойному освобождению и краху.1651>1652> Начиная с Python 3.4, рекомендуется не помещать сложный код финализации в [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), а вместо этого использовать новый метод типа [`tp_finalize`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_finalize).1653>1654> > **См. также**1655> >1656> > [**PEP 442**](https://python-all.ru/3.4/extending/newtypes.html) описывает новую схему финализации.16571658### 2.2.2. Представление объекта16591660В Python есть два способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.4/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str). (Функция [`print()`](https://python-all.ru/3.4/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str).) Оба обработчика необязательны.16611662```c1663reprfunc tp_repr;1664reprfunc tp_str;1665```16661667Обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызывается. Вот простой пример:16681669```c1670static PyObject *1671newdatatype_repr(newdatatypeobject * obj)1672{1673    return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1674                                obj->obj_UnderlyingDatatypePtr->size);1675}1676```16771678Если обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr) не указан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_name) типа и уникальный идентификатор объекта.16791680Обработчик [`tp_str`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_str) для [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str) – это то же самое, что описанный выше обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr) для [`repr()`](https://python-all.ru/3.4/library/functions.html#repr); то есть он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.4/library/stdtypes.html#str) на экземпляре вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr), но результирующая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_str) не указан, вместо него используется обработчик [`tp_repr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_repr).16811682Вот простой пример:16831684```c1685static PyObject *1686newdatatype_str(newdatatypeobject * obj)1687{1688    return PyUnicode_FromFormat("Stringified_newdatatype{{size:\%d}}",1689                                obj->obj_UnderlyingDatatypePtr->size);1690}1691```16921693### 2.2.3. Управление атрибутами16941695Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.16961697Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, достаточно реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.16981699```c1700getattrfunc  tp_getattr;        /* char * version */1701setattrfunc  tp_setattr;1702/* ... */1703getattrofunc tp_getattro;       /* PyObject * version */1704setattrofunc tp_setattro;1705```17061707Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют обобщённые реализации, которые можно использовать для предоставления версии функций управления атрибутами с [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject). Фактическая потребность в специфичных для типа обработчиках атрибутов почти полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового обобщённого механизма.17081709#### 2.2.3.1. Общее управление атрибутами17101711Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:171217131. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready).17142. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17151716Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.17171718When [`PyType_Ready()`](https://python-all.ru/3.4/c-api/type.html#c.PyType_Ready) is called, it uses three tables referenced by the type object to create [*descriptor*](https://python-all.ru/3.4/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.4/c-api/typeobj.html#c.PyTypeObject.tp_getattro) and [`tp_setattro`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_setattro) fields *NULL* as well, allowing the base type to handle attributes.17191720Таблицы объявлены как три поля объекта типа:17211722```c1723struct PyMethodDef *tp_methods;1724struct PyMemberDef *tp_members;1725struct PyGetSetDef *tp_getset;1726```17271728Если [`tp_methods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_methods) не равен *NULL*, он должен указывать на массив структур [`PyMethodDef`](https://python-all.ru/3.4/c-api/structures.html#c.PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:17291730```c1731typedef struct PyMethodDef {1732    char        *ml_name;       /* имя метода */1733    PyCFunction  ml_meth;       /* функция реализации */1734    int          ml_flags;      /* флаги */1735    char        *ml_doc;        /* докстринг */1736} PyMethodDef;1737```17381739Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется ещё одна запись – это сигнальный элемент, обозначающий конец массива. Поле `ml_name` сигнального элемента должно быть равно *NULL*.17401741Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:17421743```c1744typedef struct PyMemberDef {1745    char *name;1746    int   type;1747    int   offset;1748    int   flags;1749    char *doc;1750} PyMemberDef;1751```17521753Для каждой записи в таблице будет создан [*дескриптор*](https://python-all.ru/3.4/glossary.html#term-descriptor) и добавлен к типу; он сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.4/library/functions.html#type) должно содержать один из кодов типа, определённых в заголовочном файле `structmember.h`; это значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.17541755Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.17561757| Константа | Значение |1758| --- | --- |1759| `READONLY` | Никогда не доступен для записи. |1760| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1761| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1762| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |17631764Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.17651766Как и в таблице [`tp_methods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_methods), требуется сигнальный элемент со значением `name` равным *NULL*.17671768#### 2.2.3.2. Управление атрибутами, специфичное для типа17691770Для простоты здесь будет продемонстрирована только версия с `char*`; разница между версиями интерфейса с `char*` и [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject) заключается только в типе параметра name. Этот пример по сути делает то же самое, что и обобщённый пример выше, но не использует обобщённую поддержку, добавленную в Python 2.2. Он объясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, было понятно, что нужно делать.17711772Обработчик [`tp_getattr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, в которых вызывался бы метод [`__getattr__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__getattr__) класса.17731774Вот пример:17751776```c1777static PyObject *1778newdatatype_getattr(newdatatypeobject *obj, char *name)1779{1780    if (strcmp(name, "data") == 0)1781    {1782        return PyLong_FromLong(obj->data);1783    }17841785    PyErr_Format(PyExc_AttributeError,1786                 "'%.50s' object has no attribute '%.400s'",1787                 tp->tp_name, name);1788    return NULL;1789}1790```17911792Обработчик [`tp_setattr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_setattr) вызывается при вызове метода [`__setattr__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__delattr__) экземпляра класса. Когда атрибут должен быть удалён, третий параметр будет равен *NULL*. Вот пример, который просто возбуждает исключение; если бы это действительно было всё, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_setattr) должен быть установлен в *NULL*.17931794```c1795static int1796newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1797{1798    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1799    return -1;1800}1801```18021803### 2.2.4. Сравнение объектов18041805```c1806richcmpfunc tp_richcompare;1807```18081809Обработчик [`tp_richcompare`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_richcompare) вызывается, когда требуются сравнения. Он аналогичен [*методам расширенного сравнения*](https://python-all.ru/3.4/reference/datamodel.html#richcmpfuncs), таким как [`__lt__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__lt__), и также вызывается функциями [`PyObject_RichCompare()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_RichCompare) и [`PyObject_RichCompareBool()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_RichCompareBool).18101811Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор – это один из `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GT`, `Py_LT` или `Py_GT`. Она должна сравнить два объекта в соответствии с указанным оператором и вернуть `Py_True` или `Py_False`, если сравнение успешно, `Py_NotImplemented`, чтобы указать, что сравнение не реализовано и следует попробовать метод сравнения другого объекта, или *NULL*, если было установлено исключение.18121813Вот пример реализации для типа данных, который считается равным, если размер внутреннего указателя одинаков:18141815```c1816static PyObject *1817newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)1818{1819    PyObject *result;1820    int c, size1, size2;18211822    /* код для проверки, что оба аргумента имеют нужный тип1823       newdatatype опущен */18241825    size1 = obj1->obj_UnderlyingDatatypePtr->size;1826    size2 = obj2->obj_UnderlyingDatatypePtr->size;18271828    switch (op) {1829    case Py_LT: c = size1 <  size2; break;1830    case Py_LE: c = size1 <= size2; break;1831    case Py_EQ: c = size1 == size2; break;1832    case Py_NE: c = size1 != size2; break;1833    case Py_GT: c = size1 >  size2; break;1834    case Py_GE: c = size1 >= size2; break;1835    }1836    result = c ? Py_True : Py_False;1837    Py_INCREF(result);1838    return result;1839 }1840```18411842### 2.2.5. Поддержка абстрактных протоколов18431844Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [*Уровень абстрактных объектов*](https://python-all.ru/3.4/c-api/abstract.html#abstract).18451846Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)18471848```c1849PyNumberMethods   *tp_as_number;1850PySequenceMethods *tp_as_sequence;1851PyMappingMethods  *tp_as_mapping;1852```18531854Если нужно, чтобы объект мог вести себя как число, последовательность или отображение, то следует поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyMappingMethods) соответственно. Заполнить эту структуру подходящими значениями нужно самостоятельно. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.18551856```c1857hashfunc tp_hash;1858```18591860Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:18611862```c1863static long1864newdatatype_hash(newdatatypeobject *obj)1865{1866    long result;1867    result = obj->obj_UnderlyingDatatypePtr->size;1868    result = result * 3;1869    return result;1870}1871```18721873```c1874ternaryfunc tp_call;1875```18761877Эта функция вызывается, когда экземпляр вашего типа данных «вызывается». Например, если `obj1` – экземпляр вашего типа данных, а скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_call).18781879Эта функция принимает три аргумента:188018811. *arg1* – это экземпляр типа данных, который является субъектом вызова. Если вызовом является `obj1('hello')`, то *arg1* – это `obj1`.18822. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/3.4/c-api/arg.html#c.PyArg_ParseTuple).18833. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/3.4/c-api/arg.html#c.PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите [`TypeError`](https://python-all.ru/3.4/library/exceptions.html#TypeError) с сообщением о том, что именованные аргументы не поддерживаются.18841885Вот бессистемный пример реализации функции call.18861887```c1888/* Реализовать функцию вызова.1889 *    obj1 – экземпляр, принимающий вызов.1890 *    obj2 – кортеж, содержащий аргументы вызова, в данном1891 *         случае 3 строки.1892 */1893static PyObject *1894newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1895{1896    PyObject *result;1897    char *arg1;1898    char *arg2;1899    char *arg3;19001901    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1902        return NULL;1903    }1904    result = PyUnicode_FromFormat(1905        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1906        obj->obj_UnderlyingDatatypePtr->size,1907        arg1, arg2, arg3);1908    return result;1909}1910```19111912```c1913/* Итераторы */1914getiterfunc tp_iter;1915iternextfunc tp_iternext;1916```19171918Эти функции обеспечивают поддержку протокола итератора. Любой объект, который должен поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика: `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.19191920Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), следует создавать и возвращать новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, а также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.19211922Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только один раз). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.4/library/exceptions.html#StopIteration); пропуск исключения может дать несколько лучшую производительность. Если произошла реальная ошибка, он должен установить исключение и вернуть *NULL*.19231924### 2.2.6. Поддержка слабых ссылок19251926Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).19271928Чтобы объект можно было использовать в слабых ссылках, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.4/c-api/structures.html#c.PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_weaklistoffset) соответствующего объекта типа в смещение этого поля. Например, тип экземпляра определён следующей структурой:19291930```c1931typedef struct {1932    PyObject_HEAD1933    PyClassObject *in_class;       /* Объект класса */1934    PyObject      *in_dict;        /* Словарь */1935    PyObject      *in_weakreflist; /* Список слабых ссылок */1936} PyInstanceObject;1937```19381939Статически объявленный объект типа для экземпляров определяется следующим образом:19401941```c1942PyTypeObject PyInstance_Type = {1943    PyVarObject_HEAD_INIT(&PyType_Type, 0)1944    0,1945    "module.instance",19461947    /* Многое опущено для краткости... */19481949    Py_TPFLAGS_DEFAULT,                         /* tp_flags */1950    0,                                          /* tp_doc */1951    0,                                          /* tp_traverse */1952    0,                                          /* tp_clear */1953    0,                                          /* tp_richcompare */1954    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */1955};1956```19571958Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:19591960```c1961static PyObject *1962instance_new() {1963    /* Прочие детали инициализации опущены для краткости */19641965    self->in_weakreflist = NULL;19661967    return (PyObject *) self;1968}1969```19701971Единственное дополнение заключается в том, что деструктор должен вызывать менеджер слабых ссылок для очистки любых слабых ссылок. Это требуется только в том случае, если список слабых ссылок не равен *NULL*:19721973```c1974static void1975instance_dealloc(PyInstanceObject *inst)1976{1977    /* Выделить временные объекты, если нужно, но не начинать1978       уничтожение пока не начинать1979     */19801981    if (inst->in_weakreflist != NULL)1982        PyObject_ClearWeakRefs((PyObject *) inst);19831984    /* Продолжить уничтожение объекта обычным образом. */1985}1986```19871988### 2.2.7. Дополнительные рекомендации19891990Помните, что большинство этих функций можно опустить; в этом случае нужно указать `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с дистрибутивом исходного кода Python.19911992Чтобы узнать, как реализовать какой-либо конкретный метод для вашего нового типа данных, сделайте следующее: скачайте и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем найдите в файлах C `tp_` плюс нужную функцию (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.19931994Когда нужно проверить, что объект является экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_TypeCheck). Пример её использования может выглядеть следующим образом:19951996```c1997if (! PyObject_TypeCheck(some_object, &MyType)) {1998    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");1999    return NULL;2000}2001```20022003Сноски20042005| [\[1\]](https://python-all.ru/3.4/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2006| --- | --- |20072008| [\[2\]](https://python-all.ru/3.4/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.4/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы, которые позволяют «открепить» объект от сборки мусора, однако эти вызовы являются продвинутыми и не рассматриваются здесь. |2009| --- | --- |20102011| [\[3\]](https://python-all.ru/3.4/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2012| --- | --- |20132014| [\[4\]](https://python-all.ru/3.4/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2015| --- | --- |2016