> **Источник:** https://python-all.ru/3.3/extending/newtypes.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# 2. Определение новых типов

Как упоминалось в предыдущей главе, Python позволяет разработчику модуля расширения определять новые типы, которыми можно манипулировать из кода Python, подобно строкам и спискам в ядре Python.

Это несложно; код всех типов расширений следует определённому шаблону, но есть несколько деталей, которые необходимо понять, прежде чем приступать к работе.

## 2.1. Основы

Среда выполнения Python рассматривает все объекты Python как переменные типа [`PyObject*`](https://python-all.ru/3.3/c-api/structures.html#PyObject), который служит «базовым типом» для всех объектов Python. Сам [`PyObject`](https://python-all.ru/3.3/c-api/structures.html#PyObject) содержит только счётчик ссылок и указатель на «объект типа» данного объекта. Именно здесь происходит основная работа: объект типа определяет, какие (C) функции вызываются, например, когда у объекта запрашивается атрибут или он умножается на другой объект. Эти C-функции называются «методами типа».

Итак, чтобы определить новый тип объекта, необходимо создать новый объект типа.

Такие вещи лучше всего объяснять на примере, поэтому вот минимальный, но полный модуль, определяющий новый тип:

```c
#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Сюда помещаются поля, специфичные для типа. */
} noddy_NoddyObject;

static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};

static PyModuleDef noddymodule = {
    PyModuleDef_HEAD_INIT,
    "noddy",
    "Example module that creates an extension type.",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_noddy(void) 
{
    PyObject* m;

    noddy_NoddyType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&noddy_NoddyType) < 0)
        return NULL;

    m = PyModule_Create(&noddymodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&noddy_NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
    return m;
}
```

Сразу это может показаться большим объёмом, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы.

Первое, что будет новым:

```c
typedef struct {
    PyObject_HEAD
} noddy_NoddyObject;
```

Вот что будет содержать объект Noddy – в данном случае не больше, чем любой объект Python: счётчик ссылок и указатель на объект типа. Эти поля предоставляет макрос `PyObject_HEAD`. Этот макрос нужен для стандартизации расположения полей и для включения специальных отладочных полей в отладочных сборках. Обратите внимание: после макроса `PyObject_HEAD` не ставится точка с запятой – она уже включена в определение макроса. Будьте осторожны, чтобы случайно не добавить её; по привычке это легко сделать, и компилятор может не пожаловаться, но кто-нибудь другой обязательно заметит! (На Windows известно, что MSVC считает это ошибкой и отказывается компилировать код.)

Для сравнения взгляните на соответствующее определение для стандартных чисел с плавающей запятой Python:

```c
typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;
```

Далее переходим к самому главному – объекту типа.

```c
static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};
```

Теперь, если вы заглянете в определение [`PyTypeObject`](https://python-all.ru/3.3/c-api/type.html#PyTypeObject) в `object.h`, то увидите, что оно содержит гораздо больше полей, чем показано выше. Остальные поля будут заполнены нулями компилятором C, и обычно их не указывают явно, если они не нужны.

Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:

```c
PyVarObject_HEAD_INIT(NULL, 0)
```

Эта строка – небольшой изъян; хотелось бы написать:

```c
PyVarObject_HEAD_INIT(&PyType_Type, 0)
```

поскольку типом объекта типа является «type», но это не совсем соответствует стандарту C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/3.3/c-api/type.html#PyType_Ready).

```c
"noddy.Noddy",              /* tp_name */
```

Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:

```c
>>> "" + noddy.new_noddy()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot add type "noddy.Noddy" to string
```

Обратите внимание, что имя является точечным и включает как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы устанавливаем имя типа `noddy.Noddy`.

```c
sizeof(noddy_NoddyObject),  /* tp_basicsize */
```

Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New()`](https://python-all.ru/3.3/c-api/allocation.html#PyObject_New).

> **Примечание**
>
> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и при этом у вашего типа такое же значение [`tp_basicsize`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_basicsize), как у его базового типа, могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа в Python должен будет указывать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.3/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__new__) вашего типа без ошибки. Эту проблему можно избежать, если гарантировать, что значение [`tp_basicsize`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_basicsize) вашего типа больше, чем у его базового типа. В большинстве случаев это и так верно, потому что либо ваш базовый тип – [`object`](https://python-all.ru/3.3/library/functions.html#object), либо вы добавляете поля данных к базовому типу и тем самым увеличиваете его размер.

```c
0,                          /* tp_itemsize */
```

Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.

Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.3/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).

```c
Py_TPFLAGS_DEFAULT,        /* tp_flags */
```

Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые текущей версией Python.

Строку документации для типа мы указываем в [`tp_doc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_doc).

```c
"Noddy objects",           /* tp_doc */
```

Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.

Пока что всё, что нам нужно уметь делать – создавать новые объекты `Noddy`. Чтобы разрешить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new). В данном случае можно просто использовать стандартную реализацию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/3.3/c-api/type.html#PyType_GenericNew). Хотелось бы просто присвоить её слоту [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new), но из соображений переносимости мы не можем этого сделать. На некоторых платформах или компиляторах нельзя статически инициализировать член структуры функцией, определённой в другом C-модуле, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/3.3/c-api/type.html#PyType_Ready):

```c
noddy_NoddyType.tp_new = PyType_GenericNew;
if (PyType_Ready(&noddy_NoddyType) < 0)
    return;
```

Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!

Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_noddy()`:

```c
if (PyType_Ready(&noddy_NoddyType) < 0)
    return;
```

Это инициализирует тип `Noddy`, заполняя ряд членов, включая `ob_type`, который мы изначально установили в *NULL*.

```c
PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
```

Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy`, вызывая класс `Noddy`:

```c
>>> import noddy
>>> mynoddy = noddy.Noddy()
```

Вот и всё! Осталось только собрать это; поместите приведённый выше код в файл с именем `noddy.c` и

```c
from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
      ext_modules=[Extension("noddy", ["noddy.c"])])
```

в файл с именем `setup.py`; затем, набрав

```c
$ python setup.py build
```

в командной оболочке, вы получите файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поиграться с объектами Noddy.

Было не так сложно, правда?

Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.

### 2.1.1. Добавление данных и методов к базовому примеру

Давайте расширим базовый пример, добавив данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `noddy2`, который добавляет эти возможности:

```c
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* имя */
    PyObject *last;  /* фамилия */
    int number;
} Noddy;

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }

    return 0;
}

static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Страж */
};

static PyObject *
Noddy_name(Noddy* self)
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Страж */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyModuleDef noddy2module = {
    PyModuleDef_HEAD_INIT,
    "noddy2",
    "Example module that creates an extension type.",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_noddy2(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return NULL;

    m = PyModule_Create(&noddy2module);
    if (m == NULL)
        return NULL;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
    return m;
}
```

Эта версия модуля содержит ряд изменений.

Мы добавили дополнительный include:

```c
#include <structmember.h>
```

Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.

Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа сокращено до `NoddyType`.

Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* – это строки Python, содержащие имя и фамилию. Атрибут *number* – целое число.

Структура объекта обновляется соответствующим образом:

```c
typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;
```

Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:

```c
static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}
```

которое присваивается члену [`tp_dealloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_dealloc):

```c
(destructor)Noddy_dealloc, /*tp_dealloc*/
```

Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/3.3/c-api/refcounting.html#Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, потому что объект может быть экземпляром подкласса.

Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:

```c
static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}
```

и устанавливаем его в член [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new):

```c
Noddy_new,                 /* tp_new */
```

Член new отвечает за создание (в отличие от инициализации) объектов типа. В Python он доступен как метод [`__new__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__new__). Подробное обсуждение метода [`__new__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__new__) приведено в статье «Unifying types and classes in Python». Одна из причин реализовать метод new – гарантировать начальные значения переменных экземпляра. В данном случае мы используем метод new, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/3.3/c-api/type.html#PyType_GenericNew) в качестве метода new, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/3.3/c-api/type.html#PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.

Метод new – это статический метод, который получает тип, создаваемый в данный момент, и любые аргументы, переданные при вызове типа, и возвращает новый созданный объект. Методы new всегда принимают позиционные и ключевые аргументы, но часто игнорируют их, оставляя обработку аргументов методам инициализации. Обратите внимание: если тип поддерживает наследование, переданный тип может не быть тем типом, который определяется. Метод new вызывает слот [`tp_alloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_alloc) для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.3/c-api/type.html#PyType_Ready) заполняет его, наследуя от нашего базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.3/library/functions.html#object). Большинство типов использует выделение по умолчанию.

> **Примечание**
>
> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new) базового типа или [`__new__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__new__)), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не сделать, подклассы вашего типа в Python, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.3/library/exceptions.html#TypeError).)

Мы предоставляем функцию инициализации:

```c
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }

    return 0;
}
```

заполнив слот [`tp_init`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_init).

```c
(initproc)Noddy_init,         /* tp_init */
```

Слот [`tp_init`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от метода new, нельзя гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке объектов и может быть переопределён. Наш инициализатор принимает аргументы для начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы.

Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__init__) на наших объектах. Поэтому нужно быть особенно осторожными при присваивании новых значений. Например, можно было бы попытаться присвоить члену `first` значение так:

```c
if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}
```

Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому он может быть объектом любого типа. У него может быть деструктор, который вызывает код, пытающийся обратиться к члену `first`. Чтобы быть предусмотрительными и защититься от такой возможности, мы почти всегда переприсваиваем члены перед уменьшением их счётчиков ссылок. Когда же этого делать не нужно?

- когда точно известно, что счётчик ссылок больше 1
- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.3/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа
- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.3/extending/newtypes.html#id8)

Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:

```c
static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Страж */
};
```

и поместите определения в слот [`tp_members`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_members):

```c
Noddy_members,             /* tp_members */
```

Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [*Управление атрибутами общего назначения*](https://python-all.ru/3.3/extending/newtypes.html#generic-attribute-management) ниже.

Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.

Определяем один метод `name()`, который выводит имя объекта как объединение имени и фамилии.

```c
static PyObject *
Noddy_name(Noddy* self)
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
```

Метод реализован как функция на C, которая принимает экземпляр `Noddy` (или подкласса `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в принятии кортежа позиционных аргументов или словаря именованных аргументов. Этот метод эквивалентен методу Python:

```c
def name(self):
   return "%s %s" % (self.first, self.last)
```

Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше предотвратить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать, в следующем разделе.

Теперь, когда мы определили метод, нужно создать массив определений методов:

```c
static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Страж */
};
```

и назначаем их в слот [`tp_methods`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_methods):

```c
Noddy_methods,             /* tp_methods */
```

Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.3/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаются аргументы.

Наконец, сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы осторожно, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.3/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флагов нашего класса:

```c
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
```

Переименовываем `PyInit_noddy()` в `PyInit_noddy2()` и обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.3/c-api/module.html#PyModuleDef).

Наконец, обновляем наш файл `setup.py` для сборки нового модуля:

```c
from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
      ext_modules=[
         Extension("noddy", ["noddy.c"]),
         Extension("noddy2", ["noddy2.c"]),
         ])
```

### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных

В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.

```c
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }

    return 0;
}

static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Страж */
};

static PyObject *
Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }

    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->first);
    Py_INCREF(value);
    self->first = value;

    return 0;
}

static PyObject *
Noddy_getlast(Noddy *self, void *closure)
{
    Py_INCREF(self->last);
    return self->last;
}

static int
Noddy_setlast(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }

    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->last);
    Py_INCREF(value);
    self->last = value;

    return 0;
}

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Страж */
};

static PyObject *
Noddy_name(Noddy* self)
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Страж */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    Noddy_getseters,           /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyModuleDef noddy3module = {
    PyModuleDef_HEAD_INIT,
    "noddy3",
    "Example module that creates an extension type.",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_noddy3(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return NULL;

    m = PyModule_Create(&noddy3module);
    if (m == NULL)
        return NULL;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
    return m;
}
```

Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:

```c
Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
  if (value == NULL) {
    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
    return -1;
  }

  if (! PyUnicode_Check(value)) {
    PyErr_SetString(PyExc_TypeError,
                    "The first attribute value must be a str");
    return -1;
  }

  Py_DECREF(self->first);
  Py_INCREF(value);
  self->first = value;

  return 0;
}
```

Функции получения передаётся объект `Noddy` и «замыкание», которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает расширенное использование, при котором данные определения передаются функциям получения и установки. Это, например, можно использовать, чтобы позволить единому набору функций получения и установки решать, какой атрибут получать или устанавливать, на основе данных в замыкании.)

Функции установки передаётся объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, в этом случае атрибут удаляется. В нашей функции установки мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.

Создаём массив структур `PyGetSetDef`:

```c
static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Страж */
};
```

и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_getset):

```c
Noddy_getseters,           /* tp_getset */
```

для регистрации геттеров и сеттеров наших атрибутов.

Последний элемент структуры `PyGetSetDef` – это упомянутое выше замыкание. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.

Также удаляем определения членов для этих атрибутов:

```c
static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Страж */
};
```

Также необходимо обновить обработчик [`tp_init`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_init), чтобы разрешить передачу только строк [\[3\]](https://python-all.ru/3.3/extending/newtypes.html#id9):

```c
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }

    return 0;
}
```

Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значения *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.3/c-api/refcounting.html#Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.3/c-api/refcounting.html#Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность того, что инициализация этих членов в конструкторе не удалась.

Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как мы делали ранее, и добавляем дополнительное определение в файл `setup.py`.

### 2.1.3. Поддержка циклической сборки мусора

В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:

```c
>>> l = []
>>> l.append(l)
>>> del l
```

В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.

Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.3/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:

```c
>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l
```

Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типам необходимо заполнить два слота и установить флаг класса, включающий эти слоты:

```c
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    int vret;

    if (self->first) {
        vret = visit(self->first, arg);
        if (vret != 0)
            return vret;
    }
    if (self->last) {
        vret = visit(self->last, arg);
        if (vret != 0)
            return vret;
    }

    return 0;
}

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

static void
Noddy_dealloc(Noddy* self)
{
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }

    return 0;
}

static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Страж */
};

static PyObject *
Noddy_name(Noddy* self)
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Страж */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE |
        Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    "Noddy objects",           /* tp_doc */
    (traverseproc)Noddy_traverse,   /* tp_traverse */
    (inquiry)Noddy_clear,           /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyModuleDef noddy4module = {
    PyModuleDef_HEAD_INIT,
    "noddy4",
    "Example module that creates an extension type.",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_noddy4(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return NULL;

    m = PyModule_Create(&noddy4module);
    if (m == NULL)
        return NULL;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
    return m;
}
```

Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:

```c
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    int vret;

    if (self->first) {
        vret = visit(self->first, arg);
        if (vret != 0)
            return vret;
    }
    if (self->last) {
        vret = visit(self->last, arg);
        if (vret != 0)
            return vret;
    }

    return 0;
}
```

Для каждого подобъекта, который может участвовать в циклах, необходимо вызвать функцию `visit()`, которая передаётся методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целочисленное значение, которое должно быть возвращено, если оно не равно нулю.

Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.3/c-api/gcsupport.html#Py_VISIT), автоматизирующий вызов функций обхода. С помощью [`Py_VISIT()`](https://python-all.ru/3.3/c-api/gcsupport.html#Py_VISIT) функцию `Noddy_traverse()` можно упростить:

```c
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}
```

> **Примечание**
>
> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.3/c-api/gcsupport.html#Py_VISIT). Это сделано для поощрения единообразия в этих однообразных реализациях.

Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах. Реализуем этот метод и перереализуем деаллокатор для его использования:

```c
static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

static void
Noddy_dealloc(Noddy* self)
{
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}
```

Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы установить каждый член в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это, потому что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратный вызов в объект. Кроме того, поскольку теперь мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, который запускает сборку мусора. Если сборка мусора запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_traverse). Мы не можем рисковать тем, что `Noddy_traverse()` будет вызвана, когда счётчик ссылок члена упал до нуля, а его значение ещё не установлено в *NULL*.

Python предоставляет макрос [`Py_CLEAR()`](https://python-all.ru/3.3/c-api/refcounting.html#Py_CLEAR), автоматизирующий аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/3.3/c-api/refcounting.html#Py_CLEAR) функцию `Noddy_clear()` можно упростить:

```c
static int
Noddy_clear(Noddy *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}
```

Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.3/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:

```c
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */
```

В общем, это всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_free), их нужно было бы изменить для циклической сборки мусора. Большинство расширений будут использовать автоматически предоставленные версии.

### 2.1.4. Наследование от других типов

Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужную ему структуру `PyTypeObject`. Совместное использование этих структур `PyTypeObject` между модулями расширений может быть сложным.

В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.3/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.

```c
>>> import shoddy
>>> s = shoddy.Shoddy(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
```

```c
#include <Python.h>

typedef struct {
    PyListObject list;
    int state;
} Shoddy;

static PyObject *
Shoddy_increment(Shoddy *self, PyObject *unused)
{
    self->state++;
    return PyLong_FromLong(self->state);
}

static PyMethodDef Shoddy_methods[] = {
    {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,
     PyDoc_STR("increment state counter")},
    {NULL,	NULL},
};

static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
        return -1;
    self->state = 0;
    return 0;
}

static PyTypeObject ShoddyType = {
    PyObject_HEAD_INIT(NULL)
    "shoddy.Shoddy",         /* tp_name */
    sizeof(Shoddy),          /* tp_basicsize */
    0,                       /* tp_itemsize */
    0,                       /* tp_dealloc */
    0,                       /* tp_print */
    0,                       /* tp_getattr */
    0,                       /* tp_setattr */
    0,                       /* tp_reserved */
    0,                       /* tp_repr */
    0,                       /* tp_as_number */
    0,                       /* tp_as_sequence */
    0,                       /* tp_as_mapping */
    0,                       /* tp_hash */
    0,                       /* tp_call */
    0,                       /* tp_str */
    0,                       /* tp_getattro */
    0,                       /* tp_setattro */
    0,                       /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                       /* tp_doc */
    0,                       /* tp_traverse */
    0,                       /* tp_clear */
    0,                       /* tp_richcompare */
    0,                       /* tp_weaklistoffset */
    0,                       /* tp_iter */
    0,                       /* tp_iternext */
    Shoddy_methods,          /* tp_methods */
    0,                       /* tp_members */
    0,                       /* tp_getset */
    0,                       /* tp_base */
    0,                       /* tp_dict */
    0,                       /* tp_descr_get */
    0,                       /* tp_descr_set */
    0,                       /* tp_dictoffset */
    (initproc)Shoddy_init,   /* tp_init */
    0,                       /* tp_alloc */
    0,                       /* tp_new */
};

static PyModuleDef shoddymodule = {
    PyModuleDef_HEAD_INIT,
    "shoddy",
    "Shoddy module",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_shoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return NULL;

    m = PyModule_Create(&shoddymodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
    return m;
}
```

Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.

```c
typedef struct {
    PyListObject list;
    int state;
} Shoddy;
```

Основное различие для объектов производных типов заключается в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже включает [`PyObject_HEAD()`](https://python-all.ru/3.3/c-api/structures.html#PyObject_HEAD) в начале своей структуры.

Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно приводить как к *PyListObject\**, так и к *Shoddy\**.

```c
static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
       return -1;
    self->state = 0;
    return 0;
}
```

В методе `__init__` нашего типа видно, как вызвать метод `__init__` базового типа.

Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен непосредственно выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_alloc) – это будет сделано базовым классом при вызове его [`tp_new`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_new).

При заполнении структуры [`PyTypeObject()`](https://python-all.ru/3.3/c-api/type.html#PyTypeObject) для типа `Shoddy` можно увидеть слот для `tp_base()`. Из-за проблем с кроссплатформенными компиляторами нельзя напрямую заполнять это поле значением [`PyList_Type()`](https://python-all.ru/3.3/c-api/list.html#PyList_Type); это можно сделать позже в функции `init()` модуля.

```c
PyMODINIT_FUNC
PyInit_shoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return NULL;

    m = PyModule_Create(&shoddymodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
    return m;
}
```

Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.3/c-api/type.html#PyType_Ready) в структуре типа должен быть заполнен слот [`tp_base`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_base). При создании нового типа необязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_alloc) значением [`PyType_GenericNew()`](https://python-all.ru/3.3/c-api/type.html#PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.

После этого вызов [`PyType_Ready()`](https://python-all.ru/3.3/c-api/type.html#PyType_Ready) и добавление объекта типа в модуль ничем не отличаются от базовых примеров с `Noddy`.

## 2.2. Методы типа

Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.

Ниже приведено определение [`PyTypeObject`](https://python-all.ru/3.3/c-api/type.html#PyTypeObject), в котором опущены некоторые поля, используемые только в отладочных сборках:

```c
typedef struct _typeobject {
    PyObject_VAR_HEAD
    char *tp_name; /* Для вывода в формате "<module>.<name>" */
    int tp_basicsize, tp_itemsize; /* Для выделения памяти */

    /* Методы для реализации стандартных операций */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    void *tp_reserved;
    reprfunc tp_repr;

    /* Наборы методов для стандартных классов */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* Дополнительные стандартные операции (здесь для двоичной совместимости) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Функции для доступа к объекту как к буферу ввода/вывода */
    PyBufferProcs *tp_as_buffer;

    /* Флаги для определения наличия опциональных/расширенных возможностей */
    long tp_flags;

    char *tp_doc; /* Строка документации */

    /* вызов функции для всех доступных объектов */
    traverseproc tp_traverse;

    /* удаление ссылок на содержащиеся объекты */
    inquiry tp_clear;

    /* расширенные сравнения */
    richcmpfunc tp_richcompare;

    /* включение слабых ссылок */
    long tp_weaklistoffset;

    /* Итераторы */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Дескриптор атрибутов и механизмы подклассов */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    long tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */
    inquiry tp_is_gc; /* Для PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* порядок разрешения методов */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;

} PyTypeObject;
```

Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.

Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.

```c
char *tp_name; /* Для вывода */
```

Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!

```c
int tp_basicsize, tp_itemsize; /* Для выделения памяти */
```

Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов данного типа. В Python есть встроенная поддержка структур переменной длины (например, строки, списки), для которой предназначено поле [`tp_itemsize`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_itemsize). Об этом будет рассказано позже.

```c
char *tp_doc;
```

Сюда можно поместить строку (или её адрес), которая должна возвращаться, когда скрипт на Python обращается к `obj.__doc__` для получения строки документации.

Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.

### 2.2.1. Финализация и освобождение памяти

```c
destructor tp_dealloc;
```

Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа падает до нуля и интерпретатор Python хочет освободить его. Если у вашего типа есть память для освобождения или другие действия по очистке, поместите их сюда. Сам объект также должен быть освобождён здесь. Вот пример этой функции:

```c
static void
newdatatype_dealloc(newdatatypeobject * obj)
{
    free(obj->obj_UnderlyingDatatypePtr);
    Py_TYPE(obj)->tp_free(obj);
}
```

Важное требование к функции деаллокатора – она не должна затрагивать ожидающие исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при нормальных возвратах), не предпринимается никаких действий для защиты деаллокаторов от того, что исключение уже установлено. Любые действия, выполняемые деаллокатором, которые могут привести к выполнению дополнительного кода Python, могут обнаружить, что исключение было установлено. Это может привести к ложным ошибкам интерпретатора. Правильный способ защиты – сохранить ожидающее исключение перед выполнением опасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/3.3/c-api/exceptions.html#PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/3.3/c-api/exceptions.html#PyErr_Restore):

```c
static void
my_dealloc(PyObject *obj)
{
    MyObject *self = (MyObject *) obj;
    PyObject *cbresult;

    if (self->my_callback != NULL) {
        PyObject *err_type, *err_value, *err_traceback;

        /* Сохраняет текущее состояние исключения */
        PyErr_Fetch(&err_type, &err_value, &err_traceback);

        cbresult = PyObject_CallObject(self->my_callback, NULL);
        if (cbresult == NULL)
            PyErr_WriteUnraisable(self->my_callback);
        else
            Py_DECREF(cbresult);

        /* Восстанавливает сохранённое состояние исключения */
        PyErr_Restore(err_type, err_value, err_traceback);

        Py_DECREF(self->my_callback);
    }
    Py_TYPE(obj)->tp_free((PyObject*)self);
}
```

### 2.2.2. Представление объекта

В Python есть два способа получить текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.3/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.3/library/stdtypes.html#str). (Функция [`print()`](https://python-all.ru/3.3/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.3/library/stdtypes.html#str).) Оба обработчика необязательны.

```c
reprfunc tp_repr;
reprfunc tp_str;
```

Обработчик [`tp_repr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызывается. Вот простой пример:

```c
static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
    return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:\%d}}",
                                obj->obj_UnderlyingDatatypePtr->size);
}
```

Если обработчик [`tp_repr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_repr) не указан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_name) типа и уникальный идентификатор объекта.

Обработчик [`tp_str`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_str) для [`str()`](https://python-all.ru/3.3/library/stdtypes.html#str) – это то же самое, что описанный выше обработчик [`tp_repr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_repr) для [`repr()`](https://python-all.ru/3.3/library/functions.html#repr); то есть он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.3/library/stdtypes.html#str) на экземпляре вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_repr), но результирующая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_str) не указан, вместо него используется обработчик [`tp_repr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_repr).

Вот простой пример:

```c
static PyObject *
newdatatype_str(newdatatypeobject * obj)
{
    return PyUnicode_FromFormat("Stringified_newdatatype{{size:\%d}}",
                                obj->obj_UnderlyingDatatypePtr->size);
}
```

### 2.2.3. Управление атрибутами

Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.

Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, достаточно реализовать функции только для одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.3/c-api/structures.html#PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.

```c
getattrfunc  tp_getattr;        /* char * version */
setattrfunc  tp_setattr;
/* ... */
getattrofunc tp_getattro;       /* PyObject * version */
setattrofunc tp_setattro;
```

Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют обобщённые реализации, которые можно использовать для предоставления версии функций управления атрибутами с [`PyObject*`](https://python-all.ru/3.3/c-api/structures.html#PyObject). Фактическая потребность в специфичных для типа обработчиках атрибутов почти полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового обобщённого механизма.

#### 2.2.3.1. Общее управление атрибутами

Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:

1. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/3.3/c-api/type.html#PyType_Ready).
2. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.

Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.

When [`PyType_Ready()`](https://python-all.ru/3.3/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.3/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.3/c-api/typeobj.html#PyTypeObject.tp_getattro) and [`tp_setattro`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_setattro) fields *NULL* as well, allowing the base type to handle attributes.

Таблицы объявлены как три поля объекта типа:

```c
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
```

Если [`tp_methods`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_methods) не равен *NULL*, он должен указывать на массив структур [`PyMethodDef`](https://python-all.ru/3.3/c-api/structures.html#PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:

```c
typedef struct PyMethodDef {
    char        *ml_name;       /* имя метода */
    PyCFunction  ml_meth;       /* функция реализации */
    int          ml_flags;      /* флаги */
    char        *ml_doc;        /* докстринг */
} PyMethodDef;
```

Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется ещё одна запись – это сигнальный элемент, обозначающий конец массива. Поле `ml_name` сигнального элемента должно быть равно *NULL*.

Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:

```c
typedef struct PyMemberDef {
    char *name;
    int   type;
    int   offset;
    int   flags;
    char *doc;
} PyMemberDef;
```

Для каждой записи в таблице будет создан [*дескриптор*](https://python-all.ru/3.3/glossary.html#term-descriptor) и добавлен к типу; он сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.3/library/functions.html#type) должно содержать один из кодов типа, определённых в заголовочном файле `structmember.h`; это значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.

Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.

| Константа | Значение |
| --- | --- |
| `READONLY` | Никогда не доступен для записи. |
| `READ_RESTRICTED` | Не читается в ограниченном режиме. |
| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |
| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |

Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.

Как и в таблице [`tp_methods`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_methods), требуется сигнальный элемент со значением `name` равным *NULL*.

#### 2.2.3.2. Управление атрибутами, специфичное для типа

Для простоты здесь будет продемонстрирована только версия с `char*`; разница между версиями интерфейса с `char*` и [`PyObject*`](https://python-all.ru/3.3/c-api/structures.html#PyObject) заключается только в типе параметра name. Этот пример по сути делает то же самое, что и обобщённый пример выше, но не использует обобщённую поддержку, добавленную в Python 2.2. Он объясняет, как вызываются функции-обработчики, чтобы, если потребуется расширить их функциональность, было понятно, что нужно делать.

Обработчик [`tp_getattr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, в которых вызывался бы метод [`__getattr__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__getattr__) класса.

Вот пример:

```c
static PyObject *
newdatatype_getattr(newdatatypeobject *obj, char *name)
{
    if (strcmp(name, "data") == 0)
    {
        return PyInt_FromLong(obj->data);
    }

    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object has no attribute '%.400s'",
                 tp->tp_name, name);
    return NULL;
}
```

Обработчик [`tp_setattr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_setattr) вызывается при вызове метода [`__setattr__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__delattr__) экземпляра класса. Когда атрибут должен быть удалён, третий параметр будет равен *NULL*. Вот пример, который просто возбуждает исключение; если бы это действительно было всё, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_setattr) должен быть установлен в *NULL*.

```c
static int
newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
{
    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);
    return -1;
}
```

### 2.2.4. Сравнение объектов

```c
richcmpfunc tp_richcompare;
```

Обработчик [`tp_richcompare`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_richcompare) вызывается, когда требуются сравнения. Он аналогичен [*методам расширенного сравнения*](https://python-all.ru/3.3/reference/datamodel.html#richcmpfuncs), таким как [`__lt__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__lt__), и также вызывается функциями [`PyObject_RichCompare()`](https://python-all.ru/3.3/c-api/object.html#PyObject_RichCompare) и [`PyObject_RichCompareBool()`](https://python-all.ru/3.3/c-api/object.html#PyObject_RichCompareBool).

Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор – это один из `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GT`, `Py_LT` или `Py_GT`. Она должна сравнить два объекта в соответствии с указанным оператором и вернуть `Py_True` или `Py_False`, если сравнение успешно, `Py_NotImplemented`, чтобы указать, что сравнение не реализовано и следует попробовать метод сравнения другого объекта, или *NULL*, если было установлено исключение.

Вот пример реализации для типа данных, который считается равным, если размер внутреннего указателя одинаков:

```c
static PyObject *
newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)
{
    PyObject *result;
    int c, size1, size2;

    /* код для проверки, что оба аргумента имеют нужный тип
       newdatatype опущен */

    size1 = obj1->obj_UnderlyingDatatypePtr->size;
    size2 = obj2->obj_UnderlyingDatatypePtr->size;

    switch (op) {
    case Py_LT: c = size1 <  size2; break;
    case Py_LE: c = size1 <= size2; break;
    case Py_EQ: c = size1 == size2; break;
    case Py_NE: c = size1 != size2; break;
    case Py_GT: c = size1 >  size2; break;
    case Py_GE: c = size1 >= size2; break;
    }
    result = c ? Py_True : Py_False;
    Py_INCREF(result);
    return result;
 }
```

### 2.2.5. Поддержка абстрактных протоколов

Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [*Уровень абстрактных объектов*](https://python-all.ru/3.3/c-api/abstract.html#abstract).

Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)

```c
PyNumberMethods   *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods  *tp_as_mapping;
```

Если нужно, чтобы объект мог вести себя как число, последовательность или отображение, то следует поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.3/c-api/typeobj.html#PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.3/c-api/typeobj.html#PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.3/c-api/typeobj.html#PyMappingMethods) соответственно. Заполнить эту структуру подходящими значениями нужно самостоятельно. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.

```c
hashfunc tp_hash;
```

Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:

```c
static long
newdatatype_hash(newdatatypeobject *obj)
{
    long result;
    result = obj->obj_UnderlyingDatatypePtr->size;
    result = result * 3;
    return result;
}
```

```c
ternaryfunc tp_call;
```

Эта функция вызывается, когда экземпляр вашего типа данных «вызывается». Например, если `obj1` – экземпляр вашего типа данных, а скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_call).

Эта функция принимает три аргумента:

1. *arg1* – это экземпляр типа данных, который является субъектом вызова. Если вызовом является `obj1('hello')`, то *arg1* – это `obj1`.
2. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/3.3/c-api/arg.html#PyArg_ParseTuple).
3. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/3.3/c-api/arg.html#PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите [`TypeError`](https://python-all.ru/3.3/library/exceptions.html#TypeError) с сообщением о том, что именованные аргументы не поддерживаются.

Вот бессистемный пример реализации функции call.

```c
/* Реализовать функцию вызова.
 *    obj1 – экземпляр, принимающий вызов.
 *    obj2 – кортеж, содержащий аргументы вызова, в данном
 *         случае 3 строки.
 */
static PyObject *
newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
{
    PyObject *result;
    char *arg1;
    char *arg2;
    char *arg3;

    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
        return NULL;
    }
    result = PyUnicode_FromFormat(
        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
        obj->obj_UnderlyingDatatypePtr->size,
        arg1, arg2, arg3);
    return result;
}
```

```c
/* Итераторы */
getiterfunc tp_iter;
iternextfunc tp_iternext;
```

Эти функции обеспечивают поддержку протокола итератора. Любой объект, который должен поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика: `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.

Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), следует создавать и возвращать новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, а также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.

Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только один раз). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.3/library/exceptions.html#StopIteration); пропуск исключения может дать несколько лучшую производительность. Если произошла реальная ошибка, он должен установить исключение и вернуть *NULL*.

### 2.2.6. Поддержка слабых ссылок

Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).

Чтобы объект можно было использовать в слабых ссылках, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.3/c-api/structures.html#PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_weaklistoffset) соответствующего объекта типа в смещение этого поля. Например, тип экземпляра определён следующей структурой:

```c
typedef struct {
    PyObject_HEAD
    PyClassObject *in_class;       /* Объект класса */
    PyObject      *in_dict;        /* Словарь */
    PyObject      *in_weakreflist; /* Список слабых ссылок */
} PyInstanceObject;
```

Статически объявленный объект типа для экземпляров определяется следующим образом:

```c
PyTypeObject PyInstance_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    0,
    "module.instance",

    /* Многое опущено для краткости... */

    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */
};
```

Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:

```c
static PyObject *
instance_new() {
    /* Прочие детали инициализации опущены для краткости */

    self->in_weakreflist = NULL;

    return (PyObject *) self;
}
```

Единственное дополнение заключается в том, что деструктор должен вызывать менеджер слабых ссылок для очистки любых слабых ссылок. Это требуется только в том случае, если список слабых ссылок не равен *NULL*:

```c
static void
instance_dealloc(PyInstanceObject *inst)
{
    /* Выделить временные объекты, если нужно, но не начинать
       уничтожение пока не начинать
     */

    if (inst->in_weakreflist != NULL)
        PyObject_ClearWeakRefs((PyObject *) inst);

    /* Продолжить уничтожение объекта обычным образом. */
}
```

### 2.2.7. Дополнительные рекомендации

Помните, что большинство этих функций можно опустить; в этом случае нужно указать `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с дистрибутивом исходного кода Python.

Чтобы узнать, как реализовать какой-либо конкретный метод для вашего нового типа данных, сделайте следующее: скачайте и распакуйте дистрибутив исходного кода Python. Перейдите в каталог `Objects`, затем найдите в файлах C `tp_` плюс нужную функцию (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.

Когда нужно проверить, что объект является экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/3.3/c-api/object.html#PyObject_TypeCheck). Пример её использования может выглядеть следующим образом:

```c
if (! PyObject_TypeCheck(some_object, &MyType)) {
    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
    return NULL;
}
```

Сноски

| [\[1\]](https://python-all.ru/3.3/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |
| --- | --- |

| [\[2\]](https://python-all.ru/3.3/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.3/c-api/typeobj.html#PyTypeObject.tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы, которые позволяют «открепить» объект от сборки мусора, однако эти вызовы являются продвинутыми и не рассматриваются здесь. |
| --- | --- |

| [\[3\]](https://python-all.ru/3.3/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |
| --- | --- |

| [\[4\]](https://python-all.ru/3.3/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |
| --- | --- |
