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

newtypes.md

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

1> **Источник:** https://python-all.ru/3.5/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.5/c-api/structures.html#c.PyObject), который служит «базовым типом» для всех объектов Python. [`PyObject`](https://python-all.ru/3.5/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: поле с именем `ob_base` типа [`PyObject`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject). [`PyObject`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject), в свою очередь, содержит поле `ob_refcnt` и указатель на объект типа. К ним можно обращаться с помощью макросов [`Py_REFCNT`](https://python-all.ru/3.5/c-api/structures.html#c.Py_REFCNT) и [`Py_TYPE`](https://python-all.ru/3.5/c-api/structures.html#c.Py_TYPE) соответственно. Это те поля, которые вводит макрос [`PyObject_HEAD`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject_HEAD). Макрос нужен для того, чтобы стандартизировать размещение и включить специальные отладочные поля в отладочных сборках.9192Обратите внимание, что после макроса [`PyObject_HEAD`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject_HEAD) точка с запятой не ставится; она уже включена в определение макроса. Будьте осторожны, чтобы случайно не добавить её; по привычке это легко сделать, и ваш компилятор, возможно, не выдаст ошибку, но чей-то другой может! (В Windows MSVC, как известно, считает это ошибкой и отказывается компилировать код.)9394Для сравнения взгляните на соответствующее определение для стандартных чисел с плавающей запятой Python:9596```c97typedef struct {98    PyObject_HEAD99    double ob_fval;100} PyFloatObject;101```102103Далее переходим к самому главному – объекту типа.104105```c106static PyTypeObject noddy_NoddyType = {107    PyVarObject_HEAD_INIT(NULL, 0)108    "noddy.Noddy",             /* tp_name */109    sizeof(noddy_NoddyObject), /* tp_basicsize */110    0,                         /* tp_itemsize */111    0,                         /* tp_dealloc */112    0,                         /* tp_print */113    0,                         /* tp_getattr */114    0,                         /* tp_setattr */115    0,                         /* tp_as_async */116    0,                         /* tp_repr */117    0,                         /* tp_as_number */118    0,                         /* tp_as_sequence */119    0,                         /* tp_as_mapping */120    0,                         /* tp_hash  */121    0,                         /* tp_call */122    0,                         /* tp_str */123    0,                         /* tp_getattro */124    0,                         /* tp_setattro */125    0,                         /* tp_as_buffer */126    Py_TPFLAGS_DEFAULT,        /* tp_flags */127    "Noddy objects",           /* tp_doc */128};129```130131Если теперь заглянуть в определение [`PyTypeObject`](https://python-all.ru/3.5/c-api/type.html#c.PyTypeObject) в `object.h`, можно увидеть, что у него гораздо больше полей, чем в приведённом выше определении. Остальные поля будут заполнены нулями компилятором C, и общепринятой практикой является не указывать их явно, если они не нужны.132133Это настолько важно, что мы разберём его верхнюю часть ещё подробнее:134135```c136PyVarObject_HEAD_INIT(NULL, 0)137```138139Эта строка – небольшой изъян; хотелось бы написать:140141```c142PyVarObject_HEAD_INIT(&PyType_Type, 0)143```144145поскольку типом объекта типа является «type», но это не строго соответствует C, и некоторые компиляторы выдают предупреждения. К счастью, этот член будет заполнен за нас функцией [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready).146147```c148"noddy.Noddy",              /* tp_name */149```150151Имя нашего типа. Оно будет отображаться в стандартном текстовом представлении наших объектов и в некоторых сообщениях об ошибках, например:152153```c154>>> "" + noddy.new_noddy()155Traceback (most recent call last):156  File "<stdin>", line 1, in <module>157TypeError: cannot add type "noddy.Noddy" to string158```159160Обратите внимание, что имя – это составное имя, включающее как имя модуля, так и имя типа внутри модуля. В данном случае модуль – `noddy`, а тип – `Noddy`, поэтому мы задаём имя типа как `noddy.Noddy`. Один из побочных эффектов использования имени без точки состоит в том, что инструмент документации pydoc не будет отображать новый тип в документации модуля.161162```c163sizeof(noddy_NoddyObject),  /* tp_basicsize */164```165166Это нужно, чтобы Python знал, сколько памяти выделять при вызове [`PyObject_New()`](https://python-all.ru/3.5/c-api/allocation.html#c.PyObject_New).167168> **Примечание**169>170> Если вы хотите, чтобы ваш тип можно было наследовать из Python, и ваш тип имеет тот же [`tp_basicsize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_basicsize), что и его базовый тип, у вас могут возникнуть проблемы с множественным наследованием. Подкласс вашего типа на Python должен будет указать ваш тип первым в своём [`__bases__`](https://python-all.ru/3.5/library/stdtypes.html#class.__bases__), иначе он не сможет вызвать метод [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__) вашего типа без ошибки. Вы можете избежать этой проблемы, обеспечив большее значение для [`tp_basicsize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_basicsize) вашего типа по сравнению с его базовым типом. В большинстве случаев это будет верно, потому что либо ваш базовый тип будет [`object`](https://python-all.ru/3.5/library/functions.html#object), либо вы будете добавлять члены данных в базовый тип, тем самым увеличивая его размер.171172```c1730,                          /* tp_itemsize */174```175176Это относится к объектам переменной длины, таким как списки и строки. Пока проигнорируйте это.177178Пропуская ряд методов типа, которые мы не предоставляем, устанавливаем флаги класса в [`Py_TPFLAGS_DEFAULT`](https://python-all.ru/3.5/c-api/typeobj.html#Py_TPFLAGS_DEFAULT).179180```c181Py_TPFLAGS_DEFAULT,        /* tp_flags */182```183184Все типы должны включать эту константу в свои флаги. Она включает все члены, определённые до Python 3.3 как минимум. Если вам нужны дополнительные члены, вам потребуется выполнить OR с соответствующими флагами.185186Мы предоставляем строку документации для типа в [`tp_doc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_doc).187188```c189"Noddy objects",           /* tp_doc */190```191192Теперь мы переходим к методам типа – тому, что делает ваши объекты отличными от других. В этой версии модуля мы не будем реализовывать ни один из них. Позже мы расширим этот пример, добавив более интересное поведение.193194Пока всё, что нам нужно уметь делать, – создавать новые объекты `Noddy`. Чтобы включить создание объектов, необходимо предоставить реализацию [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new). В данном случае можно просто использовать реализацию по умолчанию, предоставляемую функцией API [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew). Хотелось бы просто присвоить это слоту [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new), но из соображений переносимости мы не можем статически инициализировать член структуры функцией, определённой в другом модуле C, поэтому вместо этого мы присвоим слот [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) в функции инициализации модуля непосредственно перед вызовом [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready):195196```c197noddy_NoddyType.tp_new = PyType_GenericNew;198if (PyType_Ready(&noddy_NoddyType) < 0)199    return;200```201202Все остальные методы типа равны *NULL*, поэтому мы рассмотрим их позже – это для следующего раздела!203204Всё остальное в файле должно быть знакомым, за исключением некоторого кода в `PyInit_noddy()`:205206```c207if (PyType_Ready(&noddy_NoddyType) < 0)208    return;209```210211Это инициализирует тип `Noddy`, заполняя ряд членов, включая `ob_type`, который мы изначально установили в *NULL*.212213```c214PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);215```216217Это добавляет тип в словарь модуля. Это позволяет нам создавать экземпляры `Noddy` вызовом класса `Noddy`:218219```c220>>> import noddy221>>> mynoddy = noddy.Noddy()222```223224Вот и всё! Осталось только собрать его; поместите приведённый выше код в файл с именем `noddy.c` и225226```c227from distutils.core import setup, Extension228setup(name="noddy", version="1.0",229      ext_modules=[Extension("noddy", ["noddy.c"])])230```231232в файл с именем `setup.py`; затем введите233234```console235$ python setup.py build236```237238в командной оболочке должен создать файл `noddy.so` в подкаталоге; перейдите в этот каталог и запустите Python – вы сможете выполнить `import noddy` и поэкспериментировать с объектами Noddy.239240Было не так сложно, правда?241242Конечно, текущий тип Noddy довольно неинтересен. У него нет данных и он ничего не делает. Его даже нельзя наследовать.243244### 2.1.1. Добавление данных и методов к базовому примеру245246Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль `noddy2`, который добавляет эти возможности:247248```c249#include <Python.h>250#include "structmember.h"251252typedef struct {253    PyObject_HEAD254    PyObject *first; /* имя */255    PyObject *last;  /* фамилия */256    int number;257} Noddy;258259static void260Noddy_dealloc(Noddy* self)261{262    Py_XDECREF(self->first);263    Py_XDECREF(self->last);264    Py_TYPE(self)->tp_free((PyObject*)self);265}266267static PyObject *268Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)269{270    Noddy *self;271272    self = (Noddy *)type->tp_alloc(type, 0);273    if (self != NULL) {274        self->first = PyUnicode_FromString("");275        if (self->first == NULL) {276            Py_DECREF(self);277            return NULL;278        }279280        self->last = PyUnicode_FromString("");281        if (self->last == NULL) {282            Py_DECREF(self);283            return NULL;284        }285286        self->number = 0;287    }288289    return (PyObject *)self;290}291292static int293Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)294{295    PyObject *first=NULL, *last=NULL, *tmp;296297    static char *kwlist[] = {"first", "last", "number", NULL};298299    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,300                                      &first, &last,301                                      &self->number))302        return -1;303304    if (first) {305        tmp = self->first;306        Py_INCREF(first);307        self->first = first;308        Py_XDECREF(tmp);309    }310311    if (last) {312        tmp = self->last;313        Py_INCREF(last);314        self->last = last;315        Py_XDECREF(tmp);316    }317318    return 0;319}320321static PyMemberDef Noddy_members[] = {322    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,323     "first name"},324    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,325     "last name"},326    {"number", T_INT, offsetof(Noddy, number), 0,327     "noddy number"},328    {NULL}  /* Страж */329};330331static PyObject *332Noddy_name(Noddy* self)333{334    if (self->first == NULL) {335        PyErr_SetString(PyExc_AttributeError, "first");336        return NULL;337    }338339    if (self->last == NULL) {340        PyErr_SetString(PyExc_AttributeError, "last");341        return NULL;342    }343344    return PyUnicode_FromFormat("%S %S", self->first, self->last);345}346347static PyMethodDef Noddy_methods[] = {348    {"name", (PyCFunction)Noddy_name, METH_NOARGS,349     "Return the name, combining the first and last name"350    },351    {NULL}  /* Страж */352};353354static PyTypeObject NoddyType = {355    PyVarObject_HEAD_INIT(NULL, 0)356    "noddy.Noddy",             /* tp_name */357    sizeof(Noddy),             /* tp_basicsize */358    0,                         /* tp_itemsize */359    (destructor)Noddy_dealloc, /* tp_dealloc */360    0,                         /* tp_print */361    0,                         /* tp_getattr */362    0,                         /* tp_setattr */363    0,                         /* tp_reserved */364    0,                         /* tp_repr */365    0,                         /* tp_as_number */366    0,                         /* tp_as_sequence */367    0,                         /* tp_as_mapping */368    0,                         /* tp_hash  */369    0,                         /* tp_call */370    0,                         /* tp_str */371    0,                         /* tp_getattro */372    0,                         /* tp_setattro */373    0,                         /* tp_as_buffer */374    Py_TPFLAGS_DEFAULT |375        Py_TPFLAGS_BASETYPE,   /* tp_flags */376    "Noddy objects",           /* tp_doc */377    0,                         /* tp_traverse */378    0,                         /* tp_clear */379    0,                         /* tp_richcompare */380    0,                         /* tp_weaklistoffset */381    0,                         /* tp_iter */382    0,                         /* tp_iternext */383    Noddy_methods,             /* tp_methods */384    Noddy_members,             /* tp_members */385    0,                         /* tp_getset */386    0,                         /* tp_base */387    0,                         /* tp_dict */388    0,                         /* tp_descr_get */389    0,                         /* tp_descr_set */390    0,                         /* tp_dictoffset */391    (initproc)Noddy_init,      /* tp_init */392    0,                         /* tp_alloc */393    Noddy_new,                 /* tp_new */394};395396static PyModuleDef noddy2module = {397    PyModuleDef_HEAD_INIT,398    "noddy2",399    "Example module that creates an extension type.",400    -1,401    NULL, NULL, NULL, NULL, NULL402};403404PyMODINIT_FUNC405PyInit_noddy2(void)406{407    PyObject* m;408409    if (PyType_Ready(&NoddyType) < 0)410        return NULL;411412    m = PyModule_Create(&noddy2module);413    if (m == NULL)414        return NULL;415416    Py_INCREF(&NoddyType);417    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);418    return m;419}420```421422Эта версия модуля содержит ряд изменений.423424Мы добавили дополнительный include:425426```c427#include <structmember.h>428```429430Этот include предоставляет объявления, которые мы используем для обработки атрибутов, как описано немного позже.431432Имя структуры объекта `Noddy` было сокращено до `Noddy`. Имя объекта типа было сокращено до `NoddyType`.433434Тип `Noddy` теперь имеет три атрибута данных: *first*, *last* и *number*. Переменные *first* и *last* являются строками Python, содержащими имя и фамилию. Атрибут *number* – целое число.435436Структура объекта обновляется соответствующим образом:437438```c439typedef struct {440    PyObject_HEAD441    PyObject *first;442    PyObject *last;443    int number;444} Noddy;445```446447Поскольку теперь у нас есть данные для управления, мы должны быть более внимательны к выделению и освобождению памяти объектов. Как минимум, нам нужен метод освобождения:448449```c450static void451Noddy_dealloc(Noddy* self)452{453    Py_XDECREF(self->first);454    Py_XDECREF(self->last);455    Py_TYPE(self)->tp_free((PyObject*)self);456}457```458459который присваивается члену [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc):460461```c462(destructor)Noddy_dealloc, /*tp_dealloc*/463```464465Этот метод уменьшает счётчики ссылок двух атрибутов Python. Мы используем [`Py_XDECREF()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_XDECREF) здесь, потому что члены `first` и `last` могут быть *NULL*. Затем он вызывает член [`tp_free`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_free) типа объекта, чтобы освободить память объекта. Обратите внимание, что тип объекта может не быть `NoddyType`, поскольку объект может быть экземпляром подкласса.466467Мы хотим убедиться, что имя и фамилия инициализируются пустыми строками, поэтому предоставляем новый метод:468469```c470static PyObject *471Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)472{473    Noddy *self;474475    self = (Noddy *)type->tp_alloc(type, 0);476    if (self != NULL) {477        self->first = PyUnicode_FromString("");478        if (self->first == NULL) {479            Py_DECREF(self);480            return NULL;481        }482483        self->last = PyUnicode_FromString("");484        if (self->last == NULL) {485            Py_DECREF(self);486            return NULL;487        }488489        self->number = 0;490    }491492    return (PyObject *)self;493}494```495496и устанавливаем его в член [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new):497498```c499Noddy_new,                 /* tp_new */500```501502Новый метод отвечает за создание (в отличие от инициализации) объектов типа. Он доступен в Python как метод [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__). См. статью «Unifying types and classes in Python» для подробного обсуждения метода [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__). Одна из причин реализовать новый метод – гарантировать начальные значения переменных экземпляра. В данном случае мы используем новый метод, чтобы убедиться, что начальные значения членов `first` и `last` не равны *NULL*. Если бы нас не волновало, являются ли начальные значения *NULL*, мы могли бы использовать [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew) в качестве нового метода, как и раньше. [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew) инициализирует все переменные экземпляра значением *NULL*.503504Новый метод – это статический метод, которому передаётся создаваемый тип и любые аргументы, переданные при вызове типа, и который возвращает созданный новый объект. Новые методы всегда принимают позиционные и именованные аргументы, но часто игнорируют их, оставляя обработку аргументов методам-инициализаторам. Обратите внимание: если тип поддерживает создание подклассов, переданный тип может не быть определяемым типом. Новый метод вызывает слот [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) для выделения памяти. Мы не заполняем слот [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) самостоятельно. Вместо этого [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready) заполняет его для нас, наследуя его от нашего базового класса, которым по умолчанию является [`object`](https://python-all.ru/3.5/library/functions.html#object). Большинство типов используют выделение памяти по умолчанию.505506> **Примечание**507>508> Если вы создаёте кооперативный [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) (тот, который вызывает [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) или [`__new__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__new__) базового типа), вы *не* должны пытаться определить, какой метод вызывать, используя порядок разрешения методов (MRO) во время выполнения. Всегда статически определяйте, какой тип вы собираетесь вызвать, и вызывайте его [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new) напрямую или через `type->tp_base->tp_new`. Если этого не делать, подклассы Python вашего типа, которые также наследуют от других классов, определённых в Python, могут работать некорректно. (В частности, вы можете не создать экземпляры таких подклассов без получения [`TypeError`](https://python-all.ru/3.5/library/exceptions.html#TypeError).)509510Мы предоставляем функцию инициализации:511512```c513static int514Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)515{516    PyObject *first=NULL, *last=NULL, *tmp;517518    static char *kwlist[] = {"first", "last", "number", NULL};519520    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,521                                      &first, &last,522                                      &self->number))523        return -1;524525    if (first) {526        tmp = self->first;527        Py_INCREF(first);528        self->first = first;529        Py_XDECREF(tmp);530    }531532    if (last) {533        tmp = self->last;534        Py_INCREF(last);535        self->last = last;536        Py_XDECREF(tmp);537    }538539    return 0;540}541```542543заполняя слот [`tp_init`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_init).544545```c546(initproc)Noddy_init,         /* tp_init */547```548549Слот [`tp_init`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_init) доступен в Python как метод [`__init__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__init__). Он используется для инициализации объекта после его создания. В отличие от нового метода, мы не можем гарантировать, что инициализатор будет вызван. Инициализатор не вызывается при распаковке (unpickling) объектов и может быть переопределён. Наш инициализатор принимает аргументы для задания начальных значений экземпляра. Инициализаторы всегда принимают позиционные и именованные аргументы. Инициализаторы должны возвращать 0 в случае успеха или -1 при ошибке.550551Инициализаторы могут вызываться несколько раз. Любой может вызвать метод [`__init__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__init__) наших объектов. По этой причине нужно быть особенно осторожным при присваивании новых значений. Может возникнуть искушение, например, присвоить члену `first` значение следующим образом:552553```c554if (first) {555    Py_XDECREF(self->first);556    Py_INCREF(first);557    self->first = first;558}559```560561Но это было бы рискованно. Наш тип не ограничивает тип члена `first`, поэтому им может быть любой объект. У него может быть деструктор, вызывающий выполнение кода, который пытается получить доступ к члену `first`. Чтобы быть параноиками и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением их счётчиков ссылок. Когда этого делать не нужно?562563- когда точно известно, что счётчик ссылок больше 1564- когда известно, что освобождение объекта [\[1\]](https://python-all.ru/3.5/extending/newtypes.html#id7) не вызовет никаких вызовов обратно в код нашего типа565- при уменьшении счётчика ссылок в обработчике [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), когда сборка мусора не поддерживается [\[2\]](https://python-all.ru/3.5/extending/newtypes.html#id8)566567Мы хотим предоставить доступ к переменным экземпляра как к атрибутам. Есть несколько способов сделать это. Самый простой – определить определения членов:568569```c570static PyMemberDef Noddy_members[] = {571    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,572     "first name"},573    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,574     "last name"},575    {"number", T_INT, offsetof(Noddy, number), 0,576     "noddy number"},577    {NULL}  /* Страж */578};579```580581и поместить определения в слот [`tp_members`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_members):582583```c584Noddy_members,             /* tp_members */585```586587Каждое определение члена содержит имя члена, тип, смещение, флаги доступа и строку документации. Подробнее см. раздел [Управление атрибутами общего назначения](https://python-all.ru/3.5/extending/newtypes.html#generic-attribute-management) ниже.588589Недостаток такого подхода в том, что он не позволяет ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но можно присвоить любые объекты Python. Более того, атрибуты можно удалять, устанавливая C-указатели в *NULL*. Даже если мы можем гарантировать, что члены инициализированы не-*NULL* значениями, эти члены могут быть установлены в *NULL*, если атрибуты удалены.590591Мы определяем один метод `name()`, который выводит имя объекта как конкатенацию имени и фамилии.592593```c594static PyObject *595Noddy_name(Noddy* self)596{597    if (self->first == NULL) {598        PyErr_SetString(PyExc_AttributeError, "first");599        return NULL;600    }601602    if (self->last == NULL) {603        PyErr_SetString(PyExc_AttributeError, "last");604        return NULL;605    }606607    return PyUnicode_FromFormat("%S %S", self->first, self->last);608}609```610611Метод реализован как функция на C, которая принимает экземпляр `Noddy` (или подкласс `Noddy`) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем никаких и не нуждаемся в кортеже позиционных аргументов или словаре именованных аргументов. Этот метод эквивалентен следующему методу Python:612613```c614def name(self):615   return "%s %s" % (self.first, self.last)616```617618Обратите внимание, что нужно проверять возможность того, что наши члены `first` и `last` равны *NULL*. Это связано с тем, что их можно удалить, и в этом случае они устанавливаются в *NULL*. Было бы лучше запретить удаление этих атрибутов и ограничить значения атрибутов строками. Мы увидим, как это сделать в следующем разделе.619620Теперь, когда мы определили метод, нужно создать массив определений методов:621622```c623static PyMethodDef Noddy_methods[] = {624    {"name", (PyCFunction)Noddy_name, METH_NOARGS,625     "Return the name, combining the first and last name"626    },627    {NULL}  /* Страж */628};629```630631и присваиваем их слоту [`tp_methods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_methods):632633```c634Noddy_methods,             /* tp_methods */635```636637Обратите внимание, что мы использовали флаг [`METH_NOARGS`](https://python-all.ru/3.5/c-api/structures.html#METH_NOARGS), чтобы указать, что методу не передаётся аргументов.638639Наконец, мы сделаем наш тип пригодным для использования в качестве базового класса. До сих пор мы писали наши методы аккуратно, чтобы они не делали никаких предположений о типе создаваемого или используемого объекта, поэтому всё, что нам нужно сделать, – это добавить [`Py_TPFLAGS_BASETYPE`](https://python-all.ru/3.5/c-api/typeobj.html#Py_TPFLAGS_BASETYPE) в определение флага класса:640641```c642Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/643```644645Мы переименовываем `PyInit_noddy()` в `PyInit_noddy2()` и обновляем имя модуля в структуре [`PyModuleDef`](https://python-all.ru/3.5/c-api/module.html#c.PyModuleDef).646647Наконец, мы обновляем наш файл `setup.py`, чтобы собрать новый модуль:648649```c650from distutils.core import setup, Extension651setup(name="noddy", version="1.0",652      ext_modules=[653         Extension("noddy", ["noddy.c"]),654         Extension("noddy2", ["noddy2.c"]),655         ])656```657658### 2.1.2. Обеспечение более тонкого контроля над атрибутами данных659660В этом разделе мы предоставим более тонкий контроль над тем, как устанавливаются атрибуты `first` и `last` в примере `Noddy`. В предыдущей версии нашего модуля переменные экземпляра `first` и `last` могли быть установлены в нестроковые значения или даже удалены. Мы хотим гарантировать, что эти атрибуты всегда содержат строки.661662```c663#include <Python.h>664#include "structmember.h"665666typedef struct {667    PyObject_HEAD668    PyObject *first;669    PyObject *last;670    int number;671} Noddy;672673static void674Noddy_dealloc(Noddy* self)675{676    Py_XDECREF(self->first);677    Py_XDECREF(self->last);678    Py_TYPE(self)->tp_free((PyObject*)self);679}680681static PyObject *682Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)683{684    Noddy *self;685686    self = (Noddy *)type->tp_alloc(type, 0);687    if (self != NULL) {688        self->first = PyUnicode_FromString("");689        if (self->first == NULL) {690            Py_DECREF(self);691            return NULL;692        }693694        self->last = PyUnicode_FromString("");695        if (self->last == NULL) {696            Py_DECREF(self);697            return NULL;698        }699700        self->number = 0;701    }702703    return (PyObject *)self;704}705706static int707Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)708{709    PyObject *first=NULL, *last=NULL, *tmp;710711    static char *kwlist[] = {"first", "last", "number", NULL};712713    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,714                                      &first, &last,715                                      &self->number))716        return -1;717718    if (first) {719        tmp = self->first;720        Py_INCREF(first);721        self->first = first;722        Py_DECREF(tmp);723    }724725    if (last) {726        tmp = self->last;727        Py_INCREF(last);728        self->last = last;729        Py_DECREF(tmp);730    }731732    return 0;733}734735static PyMemberDef Noddy_members[] = {736    {"number", T_INT, offsetof(Noddy, number), 0,737     "noddy number"},738    {NULL}  /* Страж */739};740741static PyObject *742Noddy_getfirst(Noddy *self, void *closure)743{744    Py_INCREF(self->first);745    return self->first;746}747748static int749Noddy_setfirst(Noddy *self, PyObject *value, void *closure)750{751    if (value == NULL) {752        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");753        return -1;754    }755756    if (! PyUnicode_Check(value)) {757        PyErr_SetString(PyExc_TypeError,758                        "The first attribute value must be a string");759        return -1;760    }761762    Py_DECREF(self->first);763    Py_INCREF(value);764    self->first = value;765766    return 0;767}768769static PyObject *770Noddy_getlast(Noddy *self, void *closure)771{772    Py_INCREF(self->last);773    return self->last;774}775776static int777Noddy_setlast(Noddy *self, PyObject *value, void *closure)778{779    if (value == NULL) {780        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");781        return -1;782    }783784    if (! PyUnicode_Check(value)) {785        PyErr_SetString(PyExc_TypeError,786                        "The last attribute value must be a string");787        return -1;788    }789790    Py_DECREF(self->last);791    Py_INCREF(value);792    self->last = value;793794    return 0;795}796797static PyGetSetDef Noddy_getseters[] = {798    {"first",799     (getter)Noddy_getfirst, (setter)Noddy_setfirst,800     "first name",801     NULL},802    {"last",803     (getter)Noddy_getlast, (setter)Noddy_setlast,804     "last name",805     NULL},806    {NULL}  /* Страж */807};808809static PyObject *810Noddy_name(Noddy* self)811{812    return PyUnicode_FromFormat("%S %S", self->first, self->last);813}814815static PyMethodDef Noddy_methods[] = {816    {"name", (PyCFunction)Noddy_name, METH_NOARGS,817     "Return the name, combining the first and last name"818    },819    {NULL}  /* Страж */820};821822static PyTypeObject NoddyType = {823    PyVarObject_HEAD_INIT(NULL, 0)824    "noddy.Noddy",             /* tp_name */825    sizeof(Noddy),             /* tp_basicsize */826    0,                         /* tp_itemsize */827    (destructor)Noddy_dealloc, /* tp_dealloc */828    0,                         /* tp_print */829    0,                         /* tp_getattr */830    0,                         /* tp_setattr */831    0,                         /* tp_reserved */832    0,                         /* tp_repr */833    0,                         /* tp_as_number */834    0,                         /* tp_as_sequence */835    0,                         /* tp_as_mapping */836    0,                         /* tp_hash  */837    0,                         /* tp_call */838    0,                         /* tp_str */839    0,                         /* tp_getattro */840    0,                         /* tp_setattro */841    0,                         /* tp_as_buffer */842    Py_TPFLAGS_DEFAULT |843        Py_TPFLAGS_BASETYPE,   /* tp_flags */844    "Noddy objects",           /* tp_doc */845    0,                         /* tp_traverse */846    0,                         /* tp_clear */847    0,                         /* tp_richcompare */848    0,                         /* tp_weaklistoffset */849    0,                         /* tp_iter */850    0,                         /* tp_iternext */851    Noddy_methods,             /* tp_methods */852    Noddy_members,             /* tp_members */853    Noddy_getseters,           /* tp_getset */854    0,                         /* tp_base */855    0,                         /* tp_dict */856    0,                         /* tp_descr_get */857    0,                         /* tp_descr_set */858    0,                         /* tp_dictoffset */859    (initproc)Noddy_init,      /* tp_init */860    0,                         /* tp_alloc */861    Noddy_new,                 /* tp_new */862};863864static PyModuleDef noddy3module = {865    PyModuleDef_HEAD_INIT,866    "noddy3",867    "Example module that creates an extension type.",868    -1,869    NULL, NULL, NULL, NULL, NULL870};871872PyMODINIT_FUNC873PyInit_noddy3(void)874{875    PyObject* m;876877    if (PyType_Ready(&NoddyType) < 0)878        return NULL;879880    m = PyModule_Create(&noddy3module);881    if (m == NULL)882        return NULL;883884    Py_INCREF(&NoddyType);885    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);886    return m;887}888```889890Чтобы обеспечить больший контроль над атрибутами `first` и `last`, мы будем использовать пользовательские функции получения и установки. Вот функции для получения и установки атрибута `first`:891892```c893Noddy_getfirst(Noddy *self, void *closure)894{895    Py_INCREF(self->first);896    return self->first;897}898899static int900Noddy_setfirst(Noddy *self, PyObject *value, void *closure)901{902  if (value == NULL) {903    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");904    return -1;905  }906907  if (! PyUnicode_Check(value)) {908    PyErr_SetString(PyExc_TypeError,909                    "The first attribute value must be a str");910    return -1;911  }912913  Py_DECREF(self->first);914  Py_INCREF(value);915  self->first = value;916917  return 0;918}919```920921Функция-getter получает объект `Noddy` и «замыкание» (closure), которое является указателем void. В данном случае замыкание игнорируется. (Замыкание поддерживает продвинутое использование, при котором данные определения передаются getter'у и setter'у. Это может, например, использоваться, чтобы позволить одному набору функций getter и setter определять, какой атрибут получать или устанавливать, на основе данных в замыкании.)922923Функция-setter получает объект `Noddy`, новое значение и замыкание. Новое значение может быть *NULL*, и в этом случае атрибут удаляется. В нашем setter'е мы вызываем ошибку, если атрибут удаляется или если значение атрибута не является строкой.924925Мы создаём массив структур `PyGetSetDef`:926927```c928static PyGetSetDef Noddy_getseters[] = {929    {"first",930     (getter)Noddy_getfirst, (setter)Noddy_setfirst,931     "first name",932     NULL},933    {"last",934     (getter)Noddy_getlast, (setter)Noddy_setlast,935     "last name",936     NULL},937    {NULL}  /* Страж */938};939```940941и регистрируем его в слоте [`tp_getset`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_getset):942943```c944Noddy_getseters,           /* tp_getset */945```946947для регистрации геттеров и сеттеров наших атрибутов.948949Последний элемент в структуре `PyGetSetDef` – это замыкание, упомянутое выше. В данном случае мы не используем замыкание, поэтому просто передаём *NULL*.950951Также удаляем определения членов для этих атрибутов:952953```c954static PyMemberDef Noddy_members[] = {955    {"number", T_INT, offsetof(Noddy, number), 0,956     "noddy number"},957    {NULL}  /* Страж */958};959```960961Нам также нужно обновить обработчик [`tp_init`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_init), чтобы разрешить передавать только строки [\[3\]](https://python-all.ru/3.5/extending/newtypes.html#id9):962963```c964static int965Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)966{967    PyObject *first=NULL, *last=NULL, *tmp;968969    static char *kwlist[] = {"first", "last", "number", NULL};970971    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,972                                      &first, &last,973                                      &self->number))974        return -1;975976    if (first) {977        tmp = self->first;978        Py_INCREF(first);979        self->first = first;980        Py_DECREF(tmp);981    }982983    if (last) {984        tmp = self->last;985        Py_INCREF(last);986        self->last = last;987        Py_DECREF(tmp);988    }989990    return 0;991}992```993994Благодаря этим изменениям мы можем гарантировать, что члены `first` и `last` никогда не будут *NULL*, поэтому мы можем удалить проверки на значение *NULL* почти во всех случаях. Это означает, что большинство вызовов [`Py_XDECREF()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_XDECREF) можно преобразовать в вызовы [`Py_DECREF()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_DECREF). Единственное место, где мы не можем изменить эти вызовы, – это деаллокатор, где существует вероятность, что инициализация этих членов завершилась неудачей в конструкторе.995996Также переименовываем функцию инициализации модуля и имя модуля в функции инициализации, как делали раньше, и добавляем дополнительное определение в файл `setup.py`.997998### 2.1.3. Поддержка циклической сборки мусора9991000В Python есть циклический сборщик мусора, который может обнаруживать ненужные объекты даже когда их счётчики ссылок не равны нулю. Это может происходить, когда объекты участвуют в циклах. Например, рассмотрим:10011002```c1003>>> l = []1004>>> l.append(l)1005>>> del l1006```10071008В этом примере мы создаём список, который содержит сам себя. Когда мы его удаляем, у него всё ещё есть ссылка от самого себя. Его счётчик ссылок не падает до нуля. К счастью, циклический сборщик мусора Python в конечном итоге определит, что список является мусором, и освободит его.10091010Во второй версии примера `Noddy` мы разрешили хранить объекты любого типа в атрибутах `first` или `last`. [\[4\]](https://python-all.ru/3.5/extending/newtypes.html#id10) Это означает, что объекты `Noddy` могут участвовать в циклах:10111012```c1013>>> import noddy21014>>> n = noddy2.Noddy()1015>>> l = [n]1016>>> n.first = l1017```10181019Это довольно глупо, но даёт нам повод добавить поддержку циклического сборщика мусора в пример `Noddy`. Для поддержки циклической сборки мусора типы должны заполнить два слота и установить классовый флаг, который включает эти слоты:10201021```c1022#include <Python.h>1023#include "structmember.h"10241025typedef struct {1026    PyObject_HEAD1027    PyObject *first;1028    PyObject *last;1029    int number;1030} Noddy;10311032static int1033Noddy_traverse(Noddy *self, visitproc visit, void *arg)1034{1035    int vret;10361037    if (self->first) {1038        vret = visit(self->first, arg);1039        if (vret != 0)1040            return vret;1041    }1042    if (self->last) {1043        vret = visit(self->last, arg);1044        if (vret != 0)1045            return vret;1046    }10471048    return 0;1049}10501051static int1052Noddy_clear(Noddy *self)1053{1054    PyObject *tmp;10551056    tmp = self->first;1057    self->first = NULL;1058    Py_XDECREF(tmp);10591060    tmp = self->last;1061    self->last = NULL;1062    Py_XDECREF(tmp);10631064    return 0;1065}10661067static void1068Noddy_dealloc(Noddy* self)1069{1070    PyObject_GC_UnTrack(self);1071    Noddy_clear(self);1072    Py_TYPE(self)->tp_free((PyObject*)self);1073}10741075static PyObject *1076Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)1077{1078    Noddy *self;10791080    self = (Noddy *)type->tp_alloc(type, 0);1081    if (self != NULL) {1082        self->first = PyUnicode_FromString("");1083        if (self->first == NULL) {1084            Py_DECREF(self);1085            return NULL;1086        }10871088        self->last = PyUnicode_FromString("");1089        if (self->last == NULL) {1090            Py_DECREF(self);1091            return NULL;1092        }10931094        self->number = 0;1095    }10961097    return (PyObject *)self;1098}10991100static int1101Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)1102{1103    PyObject *first=NULL, *last=NULL, *tmp;11041105    static char *kwlist[] = {"first", "last", "number", NULL};11061107    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,1108                                      &first, &last,1109                                      &self->number))1110        return -1;11111112    if (first) {1113        tmp = self->first;1114        Py_INCREF(first);1115        self->first = first;1116        Py_XDECREF(tmp);1117    }11181119    if (last) {1120        tmp = self->last;1121        Py_INCREF(last);1122        self->last = last;1123        Py_XDECREF(tmp);1124    }11251126    return 0;1127}11281129static PyMemberDef Noddy_members[] = {1130    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,1131     "first name"},1132    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,1133     "last name"},1134    {"number", T_INT, offsetof(Noddy, number), 0,1135     "noddy number"},1136    {NULL}  /* Страж */1137};11381139static PyObject *1140Noddy_name(Noddy* self)1141{1142    if (self->first == NULL) {1143        PyErr_SetString(PyExc_AttributeError, "first");1144        return NULL;1145    }11461147    if (self->last == NULL) {1148        PyErr_SetString(PyExc_AttributeError, "last");1149        return NULL;1150    }11511152    return PyUnicode_FromFormat("%S %S", self->first, self->last);1153}11541155static PyMethodDef Noddy_methods[] = {1156    {"name", (PyCFunction)Noddy_name, METH_NOARGS,1157     "Return the name, combining the first and last name"1158    },1159    {NULL}  /* Страж */1160};11611162static PyTypeObject NoddyType = {1163    PyVarObject_HEAD_INIT(NULL, 0)1164    "noddy.Noddy",             /* tp_name */1165    sizeof(Noddy),             /* tp_basicsize */1166    0,                         /* tp_itemsize */1167    (destructor)Noddy_dealloc, /* tp_dealloc */1168    0,                         /* tp_print */1169    0,                         /* tp_getattr */1170    0,                         /* tp_setattr */1171    0,                         /* tp_reserved */1172    0,                         /* tp_repr */1173    0,                         /* tp_as_number */1174    0,                         /* tp_as_sequence */1175    0,                         /* tp_as_mapping */1176    0,                         /* tp_hash  */1177    0,                         /* tp_call */1178    0,                         /* tp_str */1179    0,                         /* tp_getattro */1180    0,                         /* tp_setattro */1181    0,                         /* tp_as_buffer */1182    Py_TPFLAGS_DEFAULT |1183        Py_TPFLAGS_BASETYPE |1184        Py_TPFLAGS_HAVE_GC,    /* tp_flags */1185    "Noddy objects",           /* tp_doc */1186    (traverseproc)Noddy_traverse,   /* tp_traverse */1187    (inquiry)Noddy_clear,           /* tp_clear */1188    0,                         /* tp_richcompare */1189    0,                         /* tp_weaklistoffset */1190    0,                         /* tp_iter */1191    0,                         /* tp_iternext */1192    Noddy_methods,             /* tp_methods */1193    Noddy_members,             /* tp_members */1194    0,                         /* tp_getset */1195    0,                         /* tp_base */1196    0,                         /* tp_dict */1197    0,                         /* tp_descr_get */1198    0,                         /* tp_descr_set */1199    0,                         /* tp_dictoffset */1200    (initproc)Noddy_init,      /* tp_init */1201    0,                         /* tp_alloc */1202    Noddy_new,                 /* tp_new */1203};12041205static PyModuleDef noddy4module = {1206    PyModuleDef_HEAD_INIT,1207    "noddy4",1208    "Example module that creates an extension type.",1209    -1,1210    NULL, NULL, NULL, NULL, NULL1211};12121213PyMODINIT_FUNC1214PyInit_noddy4(void)1215{1216    PyObject* m;12171218    if (PyType_Ready(&NoddyType) < 0)1219        return NULL;12201221    m = PyModule_Create(&noddy4module);1222    if (m == NULL)1223        return NULL;12241225    Py_INCREF(&NoddyType);1226    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);1227    return m;1228}1229```12301231Метод обхода (traversal) предоставляет доступ к подобъектам, которые могут участвовать в циклах:12321233```c1234static int1235Noddy_traverse(Noddy *self, visitproc visit, void *arg)1236{1237    int vret;12381239    if (self->first) {1240        vret = visit(self->first, arg);1241        if (vret != 0)1242            return vret;1243    }1244    if (self->last) {1245        vret = visit(self->last, arg);1246        if (vret != 0)1247            return vret;1248    }12491250    return 0;1251}1252```12531254Для каждого подобъекта, который может участвовать в циклах, нужно вызвать функцию `visit()`, передаваемую методу обхода. Функция `visit()` принимает в качестве аргументов подобъект и дополнительный аргумент *arg*, переданный методу обхода. Она возвращает целое значение, которое должно быть возвращено, если оно ненулевое.12551256Python предоставляет макрос [`Py_VISIT()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.Py_VISIT), который автоматизирует вызов функций visit. С помощью [`Py_VISIT()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.Py_VISIT), `Noddy_traverse()` можно упростить:12571258```c1259static int1260Noddy_traverse(Noddy *self, visitproc visit, void *arg)1261{1262    Py_VISIT(self->first);1263    Py_VISIT(self->last);1264    return 0;1265}1266```12671268> **Примечание**1269>1270> Обратите внимание, что реализация [`tp_traverse`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_traverse) должна называть свои аргументы именно *visit* и *arg*, чтобы использовать [`Py_VISIT()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.Py_VISIT). Это делается для поощрения единообразия среди этих скучных реализаций.12711272Также необходимо предоставить метод для очистки любых подобъектов, которые могут участвовать в циклах.12731274```c1275static int1276Noddy_clear(Noddy *self)1277{1278    PyObject *tmp;12791280    tmp = self->first;1281    self->first = NULL;1282    Py_XDECREF(tmp);12831284    tmp = self->last;1285    self->last = NULL;1286    Py_XDECREF(tmp);12871288    return 0;1289}1290```12911292Обратите внимание на использование временной переменной в `Noddy_clear()`. Мы используем временную переменную, чтобы можно было установить каждый элемент в *NULL* перед уменьшением его счётчика ссылок. Мы делаем это потому, что, как обсуждалось ранее, если счётчик ссылок упадёт до нуля, может выполниться код, который вызовет обратно объект. Кроме того, теперь, когда мы поддерживаем сборку мусора, мы также должны учитывать возможность выполнения кода, запускающего сборку мусора. Если сборка мусора будет запущена, может быть вызван наш обработчик [`tp_traverse`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_traverse). Мы не можем рисковать тем, что функция `Noddy_traverse()` будет вызвана, когда счётчик ссылок элемента упал до нуля, а его значение ещё не установлено в *NULL*.12931294Python предоставляет функцию [`Py_CLEAR()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_CLEAR), которая автоматизирует аккуратное уменьшение счётчиков ссылок. С помощью [`Py_CLEAR()`](https://python-all.ru/3.5/c-api/refcounting.html#c.Py_CLEAR) функцию `Noddy_clear()` можно упростить:12951296```c1297static int1298Noddy_clear(Noddy *self)1299{1300    Py_CLEAR(self->first);1301    Py_CLEAR(self->last);1302    return 0;1303}1304```13051306Обратите внимание, что функция `Noddy_dealloc()` может вызывать произвольные функции через метод `__del__` или колбэк слабой ссылки. Это означает, что циклическая сборка мусора может быть запущена внутри функции. Поскольку сборщик мусора предполагает, что счётчик ссылок не равен нулю, необходимо отменить отслеживание объекта сборщиком мусора, вызвав [`PyObject_GC_UnTrack()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.PyObject_GC_UnTrack) перед очисткой элементов. Вот переопределённый деаллокатор, который использует [`PyObject_GC_UnTrack()`](https://python-all.ru/3.5/c-api/gcsupport.html#c.PyObject_GC_UnTrack) и `Noddy_clear()`.13071308```c1309static void1310Noddy_dealloc(Noddy* self)1311{1312    PyObject_GC_UnTrack(self);1313    Noddy_clear(self);1314    Py_TYPE(self)->tp_free((PyObject*)self);1315}1316```13171318Наконец, добавляем флаг [`Py_TPFLAGS_HAVE_GC`](https://python-all.ru/3.5/c-api/typeobj.html#Py_TPFLAGS_HAVE_GC) в флаги класса:13191320```c1321Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */1322```13231324Вот в основном и всё. Если бы мы написали пользовательские слоты [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) или [`tp_free`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_free), их нужно было бы модифицировать для циклической сборки мусора. Большинство расширений используют автоматически предоставляемые версии.13251326### 2.1.4. Наследование от других типов13271328Можно создавать новые типы расширений, производные от существующих типов. Проще всего наследовать от встроенных типов, поскольку расширение может легко использовать нужный ему `PyTypeObject`. Совместное использование структур `PyTypeObject` между модулями расширений может быть затруднительным.13291330В этом примере мы создадим тип `Shoddy`, наследующий от встроенного типа [`list`](https://python-all.ru/3.5/library/stdtypes.html#list). Новый тип будет полностью совместим с обычными списками, но будет иметь дополнительный метод `increment()`, увеличивающий внутренний счётчик.13311332```c1333>>> import shoddy1334>>> s = shoddy.Shoddy(range(3))1335>>> s.extend(s)1336>>> print(len(s))133761338>>> print(s.increment())133911340>>> print(s.increment())134121342```13431344```c1345#include <Python.h>13461347typedef struct {1348    PyListObject list;1349    int state;1350} Shoddy;13511352static PyObject *1353Shoddy_increment(Shoddy *self, PyObject *unused)1354{1355    self->state++;1356    return PyLong_FromLong(self->state);1357}13581359static PyMethodDef Shoddy_methods[] = {1360    {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,1361     PyDoc_STR("increment state counter")},1362    {NULL,	NULL},1363};13641365static int1366Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1367{1368    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1369        return -1;1370    self->state = 0;1371    return 0;1372}13731374static PyTypeObject ShoddyType = {1375    PyVarObject_HEAD_INIT(NULL, 0)1376    "shoddy.Shoddy",         /* tp_name */1377    sizeof(Shoddy),          /* tp_basicsize */1378    0,                       /* tp_itemsize */1379    0,                       /* tp_dealloc */1380    0,                       /* tp_print */1381    0,                       /* tp_getattr */1382    0,                       /* tp_setattr */1383    0,                       /* tp_reserved */1384    0,                       /* tp_repr */1385    0,                       /* tp_as_number */1386    0,                       /* tp_as_sequence */1387    0,                       /* tp_as_mapping */1388    0,                       /* tp_hash */1389    0,                       /* tp_call */1390    0,                       /* tp_str */1391    0,                       /* tp_getattro */1392    0,                       /* tp_setattro */1393    0,                       /* tp_as_buffer */1394    Py_TPFLAGS_DEFAULT |1395        Py_TPFLAGS_BASETYPE, /* tp_flags */1396    0,                       /* tp_doc */1397    0,                       /* tp_traverse */1398    0,                       /* tp_clear */1399    0,                       /* tp_richcompare */1400    0,                       /* tp_weaklistoffset */1401    0,                       /* tp_iter */1402    0,                       /* tp_iternext */1403    Shoddy_methods,          /* tp_methods */1404    0,                       /* tp_members */1405    0,                       /* tp_getset */1406    0,                       /* tp_base */1407    0,                       /* tp_dict */1408    0,                       /* tp_descr_get */1409    0,                       /* tp_descr_set */1410    0,                       /* tp_dictoffset */1411    (initproc)Shoddy_init,   /* tp_init */1412    0,                       /* tp_alloc */1413    0,                       /* tp_new */1414};14151416static PyModuleDef shoddymodule = {1417    PyModuleDef_HEAD_INIT,1418    "shoddy",1419    "Shoddy module",1420    -1,1421    NULL, NULL, NULL, NULL, NULL1422};14231424PyMODINIT_FUNC1425PyInit_shoddy(void)1426{1427    PyObject *m;14281429    ShoddyType.tp_base = &PyList_Type;1430    if (PyType_Ready(&ShoddyType) < 0)1431        return NULL;14321433    m = PyModule_Create(&shoddymodule);1434    if (m == NULL)1435        return NULL;14361437    Py_INCREF(&ShoddyType);1438    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1439    return m;1440}1441```14421443Как видите, исходный код очень похож на примеры `Noddy` из предыдущих разделов. Разберём основные различия между ними.14441445```c1446typedef struct {1447    PyListObject list;1448    int state;1449} Shoddy;1450```14511452Основное отличие объектов производных типов в том, что структура объекта базового типа должна быть первым значением. Базовый тип уже будет содержать [`PyObject_HEAD()`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject_HEAD) в начале своей структуры.14531454Когда объект Python является экземпляром `Shoddy`, его указатель *PyObject\** можно безопасно привести как к *PyListObject\**, так и к *Shoddy\**.14551456```c1457static int1458Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)1459{1460    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)1461       return -1;1462    self->state = 0;1463    return 0;1464}1465```14661467В методе `__init__` для нашего типа мы видим, как вызвать метод `__init__` базового типа.14681469Этот шаблон важен при написании типа с пользовательскими методами `new` и `dealloc`. Метод `new` не должен фактически выделять память для объекта с помощью [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) – это будет обработано базовым классом при вызове его метода [`tp_new`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_new).14701471При заполнении структуры [`PyTypeObject()`](https://python-all.ru/3.5/c-api/type.html#c.PyTypeObject) для типа `Shoddy` вы видите слот для `tp_base()`. Из-за проблем с кроссплатформенными компиляторами нельзя заполнять это поле напрямую с помощью [`PyList_Type()`](https://python-all.ru/3.5/c-api/list.html#c.PyList_Type); это можно сделать позже в функции `init()` модуля.14721473```c1474PyMODINIT_FUNC1475PyInit_shoddy(void)1476{1477    PyObject *m;14781479    ShoddyType.tp_base = &PyList_Type;1480    if (PyType_Ready(&ShoddyType) < 0)1481        return NULL;14821483    m = PyModule_Create(&shoddymodule);1484    if (m == NULL)1485        return NULL;14861487    Py_INCREF(&ShoddyType);1488    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);1489    return m;1490}1491```14921493Перед вызовом [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready) структура типа должна иметь заполненный слот [`tp_base`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_base). При наследовании нового типа не обязательно заполнять слот [`tp_alloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_alloc) с помощью [`PyType_GenericNew()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_GenericNew) – функция выделения памяти из базового типа будет унаследована.14941495После этого вызов [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready) и добавление объекта типа в модуль выполняется так же, как в базовых примерах `Noddy`.14961497## 2.2. Методы типа14981499Этот раздел представляет краткий обзор различных методов типа, которые можно реализовать, и их назначения.15001501Здесь приводится определение [`PyTypeObject`](https://python-all.ru/3.5/c-api/type.html#c.PyTypeObject), некоторые поля, используемые только в отладочных сборках, опущены:15021503```c1504typedef struct _typeobject {1505    PyObject_VAR_HEAD1506    const char *tp_name; /* Для вывода в формате "<module>.<name>" */1507    Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */15081509    /* Методы для реализации стандартных операций */15101511    destructor tp_dealloc;1512    printfunc tp_print;1513    getattrfunc tp_getattr;1514    setattrfunc tp_setattr;1515    PyAsyncMethods *tp_as_async; /* ранее известный как tp_compare (Python 2)1516                                    или tp_reserved (Python 3) */1517    reprfunc tp_repr;15181519    /* Наборы методов для стандартных классов */15201521    PyNumberMethods *tp_as_number;1522    PySequenceMethods *tp_as_sequence;1523    PyMappingMethods *tp_as_mapping;15241525    /* Дополнительные стандартные операции (здесь для двоичной совместимости) */15261527    hashfunc tp_hash;1528    ternaryfunc tp_call;1529    reprfunc tp_str;1530    getattrofunc tp_getattro;1531    setattrofunc tp_setattro;15321533    /* Функции для доступа к объекту как к буферу ввода/вывода */1534    PyBufferProcs *tp_as_buffer;15351536    /* Флаги для определения наличия опциональных/расширенных возможностей */1537    unsigned long tp_flags;15381539    const char *tp_doc; /* Строка документации */15401541    /* вызов функции для всех доступных объектов */1542    traverseproc tp_traverse;15431544    /* удаление ссылок на содержащиеся объекты */1545    inquiry tp_clear;15461547    /* расширенные сравнения */1548    richcmpfunc tp_richcompare;15491550    /* включение слабых ссылок */1551    Py_ssize_t tp_weaklistoffset;15521553    /* Итераторы */1554    getiterfunc tp_iter;1555    iternextfunc tp_iternext;15561557    /* Дескриптор атрибутов и механизмы подклассов */1558    struct PyMethodDef *tp_methods;1559    struct PyMemberDef *tp_members;1560    struct PyGetSetDef *tp_getset;1561    struct _typeobject *tp_base;1562    PyObject *tp_dict;1563    descrgetfunc tp_descr_get;1564    descrsetfunc tp_descr_set;1565    Py_ssize_t tp_dictoffset;1566    initproc tp_init;1567    allocfunc tp_alloc;1568    newfunc tp_new;1569    freefunc tp_free; /* Низкоуровневая процедура освобождения памяти */1570    inquiry tp_is_gc; /* Для PyObject_IS_GC */1571    PyObject *tp_bases;1572    PyObject *tp_mro; /* порядок разрешения методов */1573    PyObject *tp_cache;1574    PyObject *tp_subclasses;1575    PyObject *tp_weaklist;1576    destructor tp_del;15771578    /* Метка версии кэша атрибутов типа. Добавлено в версии 2.6. */1579    unsigned int tp_version_tag;15801581    destructor tp_finalize;15821583} PyTypeObject;1584```15851586Это *много* методов. Но не стоит слишком беспокоиться – если у вас есть тип, который вы хотите определить, весьма вероятно, что вы реализуете лишь несколько из них.15871588Как вы, вероятно, уже ожидаете, мы рассмотрим это и предоставим больше информации о различных обработчиках. Мы не будем следовать порядку их определения в структуре, потому что на порядок полей влияет много исторического наследия; убедитесь, что инициализация вашего типа сохраняет поля в правильном порядке! Чаще всего проще найти пример, который включает все необходимые поля (даже если они инициализированы значением `0`), а затем изменить значения для вашего нового типа.15891590```c1591const char *tp_name; /* Для вывода */1592```15931594Имя типа – как упоминалось в предыдущем разделе, оно будет появляться в различных местах, почти исключительно для диагностических целей. Постарайтесь выбрать что-то, что будет полезно в такой ситуации!15951596```c1597Py_ssize_t tp_basicsize, tp_itemsize; /* Для выделения памяти */1598```15991600Эти поля сообщают среде выполнения, сколько памяти выделять при создании новых объектов этого типа. Python имеет встроенную поддержку структур переменной длины (например, строки, списки), и здесь появляется поле [`tp_itemsize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_itemsize). Это будет рассмотрено позже.16011602```c1603const char *tp_doc;1604```16051606Здесь можно указать строку (или её адрес), которая будет возвращена, когда скрипт Python обратится к `obj.__doc__` для получения строки документации.16071608Теперь перейдём к базовым методам типа – тем, которые будут реализовывать большинство типов расширений.16091610### 2.2.1. Финализация и освобождение памяти16111612```c1613destructor tp_dealloc;1614```16151616Эта функция вызывается, когда счётчик ссылок на экземпляр вашего типа уменьшается до нуля и интерпретатор Python хочет освободить его. Если ваш тип требует освобождения памяти или другой очистки, этот код можно разместить здесь. Сам объект также должен быть освобождён здесь. Вот пример такой функции:16171618```c1619static void1620newdatatype_dealloc(newdatatypeobject * obj)1621{1622    free(obj->obj_UnderlyingDatatypePtr);1623    Py_TYPE(obj)->tp_free(obj);1624}1625```16261627Важное требование к функции деаллокатора: она не должна вмешиваться в уже установленные исключения. Это важно, поскольку деаллокаторы часто вызываются при раскрутке стека Python; когда стек раскручивается из-за исключения (а не при обычном возврате), ничего не делается для защиты деаллокаторов от того, что исключение уже установлено. Любые действия деаллокатора, которые могут вызвать выполнение дополнительного кода Python, могут обнаружить установленное исключение. Это может привести к вводящим в заблуждение ошибкам интерпретатора. Правильный способ защиты – сохранить текущее исключение перед выполнением небезопасного действия и восстановить его после завершения. Это можно сделать с помощью функций [`PyErr_Fetch()`](https://python-all.ru/3.5/c-api/exceptions.html#c.PyErr_Fetch) и [`PyErr_Restore()`](https://python-all.ru/3.5/c-api/exceptions.html#c.PyErr_Restore):16281629```c1630static void1631my_dealloc(PyObject *obj)1632{1633    MyObject *self = (MyObject *) obj;1634    PyObject *cbresult;16351636    if (self->my_callback != NULL) {1637        PyObject *err_type, *err_value, *err_traceback;16381639        /* Сохраняет текущее состояние исключения */1640        PyErr_Fetch(&err_type, &err_value, &err_traceback);16411642        cbresult = PyObject_CallObject(self->my_callback, NULL);1643        if (cbresult == NULL)1644            PyErr_WriteUnraisable(self->my_callback);1645        else1646            Py_DECREF(cbresult);16471648        /* Восстанавливает сохранённое состояние исключения */1649        PyErr_Restore(err_type, err_value, err_traceback);16501651        Py_DECREF(self->my_callback);1652    }1653    Py_TYPE(obj)->tp_free((PyObject*)self);1654}1655```16561657> **Примечание**1658>1659> Существуют ограничения на то, что можно безопасно делать в функции деаллокатора. Во-первых, если ваш тип поддерживает сборку мусора (с помощью [`tp_traverse`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_traverse) и/или [`tp_clear`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_clear)), некоторые члены объекта могут быть очищены или финализированы к моменту вызова [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc). Во-вторых, в [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc) объект находится в нестабильном состоянии: его счётчик ссылок равен нулю. Любой вызов нетривиального объекта или API (как в примере выше) может снова вызвать [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), что приведёт к двойному освобождению и аварийному завершению.1660>1661> Начиная с Python 3.4, рекомендуется не помещать сложный код финализации в [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), а вместо этого использовать новый метод типа [`tp_finalize`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_finalize).1662>1663> > **См. также**1664> >1665> > [**PEP 442**](https://python-all.ru/3.5/extending/newtypes.html) описывает новую схему финализации.16661667### 2.2.2. Представление объекта16681669В Python есть два способа создать текстовое представление объекта: функция [`repr()`](https://python-all.ru/3.5/library/functions.html#repr) и функция [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str). (Функция [`print()`](https://python-all.ru/3.5/library/functions.html#print) просто вызывает [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str).) Оба этих обработчика необязательны.16701671```c1672reprfunc tp_repr;1673reprfunc tp_str;1674```16751676Обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr) должен возвращать строковый объект, содержащий представление экземпляра, для которого он вызван. Вот простой пример:16771678```c1679static PyObject *1680newdatatype_repr(newdatatypeobject * obj)1681{1682    return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:\%d}}",1683                                obj->obj_UnderlyingDatatypePtr->size);1684}1685```16861687Если обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr) не задан, интерпретатор предоставит представление, использующее [`tp_name`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_name) типа и уникальный идентификатор объекта.16881689Обработчик [`tp_str`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_str) относится к [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str) так же, как описанный выше обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr) относится к [`repr()`](https://python-all.ru/3.5/library/functions.html#repr): он вызывается, когда код Python вызывает [`str()`](https://python-all.ru/3.5/library/stdtypes.html#str) для экземпляра вашего объекта. Его реализация очень похожа на функцию [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr), но итоговая строка предназначена для чтения человеком. Если [`tp_str`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_str) не указан, используется обработчик [`tp_repr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_repr).16901691Вот простой пример:16921693```c1694static PyObject *1695newdatatype_str(newdatatypeobject * obj)1696{1697    return PyUnicode_FromFormat("Stringified_newdatatype{{size:\%d}}",1698                                obj->obj_UnderlyingDatatypePtr->size);1699}1700```17011702### 2.2.3. Управление атрибутами17031704Для каждого объекта, который может поддерживать атрибуты, соответствующий тип должен предоставлять функции, управляющие разрешением атрибутов. Должна быть функция, которая может получать атрибуты (если они определены), и другая – для установки атрибутов (если установка разрешена). Удаление атрибута – это особый случай, при котором новое значение, передаваемое обработчику, равно *NULL*.17051706Python поддерживает две пары обработчиков атрибутов; типу, поддерживающему атрибуты, нужно реализовать функции только одной пары. Разница в том, что одна пара принимает имя атрибута как `char*`, а другая – как [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject). Каждый тип может использовать ту пару, которая удобнее для реализации.17071708```c1709getattrfunc  tp_getattr;        /* char * version */1710setattrfunc  tp_setattr;1711/* ... */1712getattrofunc tp_getattro;       /* PyObject * version */1713setattrofunc tp_setattro;1714```17151716Если доступ к атрибутам объекта всегда является простой операцией (это будет объяснено чуть позже), существуют универсальные реализации, которые можно использовать для предоставления версии [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject) функций управления атрибутами. Необходимость в специфических для типа обработчиках атрибутов практически полностью исчезла, начиная с Python 2.2, хотя есть много примеров, которые не были обновлены для использования нового универсального механизма.17171718#### 2.2.3.1. Общее управление атрибутами17191720Большинство типов расширений используют только *простые* атрибуты. Что делает атрибуты простыми? Нужно выполнить лишь несколько условий:172117221. Имена атрибутов должны быть известны на момент вызова [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready).17232. Не требуется специальной обработки для фиксации факта поиска или установки атрибута, и не нужно предпринимать действий в зависимости от значения.17241725Обратите внимание, что этот список не накладывает никаких ограничений на значения атрибутов, момент их вычисления или способ хранения соответствующих данных.17261727Когда вызывается [`PyType_Ready()`](https://python-all.ru/3.5/c-api/type.html#c.PyType_Ready), он использует три таблицы, на которые ссылается объект типа, для создания [дескрипторов](https://python-all.ru/3.5/glossary.html#term-descriptor), которые помещаются в словарь объекта типа. Каждый дескриптор управляет доступом к одному атрибуту объекта экземпляра. Каждая из таблиц необязательна; если все три равны *NULL*, экземпляры типа будут иметь только атрибуты, унаследованные от базового типа, и следует также установить поля [`tp_getattro`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_getattro) и [`tp_setattro`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_setattro) в *NULL*, позволяя базовому типу обрабатывать атрибуты.17281729Таблицы объявлены как три поля объекта типа:17301731```c1732struct PyMethodDef *tp_methods;1733struct PyMemberDef *tp_members;1734struct PyGetSetDef *tp_getset;1735```17361737Если [`tp_methods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_methods) не равен *NULL*, он должен ссылаться на массив структур [`PyMethodDef`](https://python-all.ru/3.5/c-api/structures.html#c.PyMethodDef). Каждая запись в таблице является экземпляром этой структуры:17381739```c1740typedef struct PyMethodDef {1741    const char  *ml_name;       /* имя метода */1742    PyCFunction  ml_meth;       /* функция реализации */1743    int          ml_flags;      /* флаги */1744    const char  *ml_doc;        /* докстринг */1745} PyMethodDef;1746```17471748Для каждого метода, предоставляемого типом, должна быть определена одна запись; для методов, унаследованных от базового типа, записи не нужны. В конце требуется одна дополнительная запись – это sentinel, отмечающий конец массива. Поле `ml_name` sentinel'а должно быть равно *NULL*.17491750Вторая таблица используется для определения атрибутов, которые напрямую отображаются на данные, хранящиеся в экземпляре. Поддерживаются различные примитивные типы C, доступ может быть только для чтения или для чтения и записи. Структуры в таблице определены так:17511752```c1753typedef struct PyMemberDef {1754    char *name;1755    int   type;1756    int   offset;1757    int   flags;1758    char *doc;1759} PyMemberDef;1760```17611762Для каждой записи в таблице будет создан [дескриптор](https://python-all.ru/3.5/glossary.html#term-descriptor) и добавлен к типу, который сможет извлекать значение из структуры экземпляра. Поле [`type`](https://python-all.ru/3.5/library/functions.html#type) должно содержать один из кодов типов, определённых в заголовке `structmember.h`; значение будет использоваться для определения того, как преобразовывать значения Python в значения C и обратно. Поле `flags` используется для хранения флагов, управляющих доступом к атрибуту.17631764Следующие константы флагов определены в `structmember.h`; их можно комбинировать с помощью побитового ИЛИ.17651766| Константа | Значение |1767| --- | --- |1768| `READONLY` | Никогда не доступен для записи. |1769| `READ_RESTRICTED` | Не читается в ограниченном режиме. |1770| `WRITE_RESTRICTED` | Не записывается в ограниченном режиме. |1771| `RESTRICTED` | Не читается и не записывается в ограниченном режиме. |17721773Интересное преимущество использования таблицы [`tp_members`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_members) для создания дескрипторов, используемых во время выполнения, заключается в том, что любой атрибут, определённый таким образом, может иметь связанную строку документации – достаточно просто указать текст в таблице. Приложение может использовать API интроспекции для получения дескриптора из объекта класса и получить строку документации через его атрибут `__doc__`.17741775Как и в таблице [`tp_methods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_methods), требуется sentinel-запись со значением `name` равным *NULL*.17761777#### 2.2.3.2. Управление атрибутами, специфичное для типа17781779Для простоты здесь будет продемонстрирована только версия `char*`; тип параметра name – единственное различие между вариантами интерфейса `char*` и [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject). Этот пример делает то же самое, что и универсальный пример выше, но не использует универсальную поддержку, добавленную в Python 2.2. В нём объясняется, как вызываются функции-обработчики, чтобы, если понадобится расширить их функциональность, было понятно, что нужно сделать.17801781Обработчик [`tp_getattr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_getattr) вызывается, когда объекту требуется поиск атрибута. Он вызывается в тех же ситуациях, что и метод [`__getattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattr__) класса.17821783Вот пример:17841785```c1786static PyObject *1787newdatatype_getattr(newdatatypeobject *obj, char *name)1788{1789    if (strcmp(name, "data") == 0)1790    {1791        return PyLong_FromLong(obj->data);1792    }17931794    PyErr_Format(PyExc_AttributeError,1795                 "'%.50s' object has no attribute '%.400s'",1796                 tp->tp_name, name);1797    return NULL;1798}1799```18001801Обработчик [`tp_setattr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_setattr) вызывается, когда вызывается метод [`__setattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__setattr__) или [`__delattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__delattr__) экземпляра класса. Если атрибут должен быть удален, третий параметр будет равен *NULL*. Вот пример, который просто вызывает исключение; если бы это действительно было все, что нужно, обработчик [`tp_setattr`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_setattr) следовало бы установить в *NULL*.18021803```c1804static int1805newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)1806{1807    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);1808    return -1;1809}1810```18111812### 2.2.4. Сравнение объектов18131814```c1815richcmpfunc tp_richcompare;1816```18171818Обработчик [`tp_richcompare`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_richcompare) вызывается, когда требуется сравнение. Он аналогичен [методам расширенного сравнения](https://python-all.ru/3.5/reference/datamodel.html#richcmpfuncs), таким как [`__lt__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__lt__), а также вызывается [`PyObject_RichCompare()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_RichCompare) и [`PyObject_RichCompareBool()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_RichCompareBool).18191820Эта функция вызывается с двумя объектами Python и оператором в качестве аргументов, где оператор – один из `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GT`, `Py_LT` или `Py_GT`. Она должна сравнить два объекта с помощью указанного оператора и вернуть `Py_True` или `Py_False`, если сравнение успешно, `Py_NotImplemented`, чтобы указать, что сравнение не реализовано и следует попробовать метод сравнения другого объекта, или *NULL*, если было установлено исключение.18211822Вот пример реализации для типа данных, который считается равным, если размер внутреннего указателя одинаков:18231824```c1825static PyObject *1826newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)1827{1828    PyObject *result;1829    int c, size1, size2;18301831    /* код для проверки, что оба аргумента имеют нужный тип1832       newdatatype опущен */18331834    size1 = obj1->obj_UnderlyingDatatypePtr->size;1835    size2 = obj2->obj_UnderlyingDatatypePtr->size;18361837    switch (op) {1838    case Py_LT: c = size1 <  size2; break;1839    case Py_LE: c = size1 <= size2; break;1840    case Py_EQ: c = size1 == size2; break;1841    case Py_NE: c = size1 != size2; break;1842    case Py_GT: c = size1 >  size2; break;1843    case Py_GE: c = size1 >= size2; break;1844    }1845    result = c ? Py_True : Py_False;1846    Py_INCREF(result);1847    return result;1848 }1849```18501851### 2.2.5. Поддержка абстрактных протоколов18521853Python поддерживает множество *абстрактных* «протоколов»; конкретные интерфейсы для их использования описаны в разделе [Уровень абстрактных объектов](https://python-all.ru/3.5/c-api/abstract.html#abstract).18541855Ряд этих абстрактных интерфейсов был определен на ранних этапах разработки реализации Python. В частности, протоколы чисел, отображений и последовательностей были частью Python с самого начала. Другие протоколы добавлялись со временем. Для протоколов, которые зависят от нескольких процедур-обработчиков из реализации типа, старые протоколы были определены как необязательные блоки обработчиков, на которые ссылается объект типа. Для более новых протоколов в основном объекте типа есть дополнительные слоты, с установленным битом флага, указывающим, что слоты присутствуют и должны проверяться интерпретатором. (Бит флага не указывает, что значения слотов не равны *NULL*. Флаг может быть установлен, чтобы указать наличие слота, но слот может оставаться незаполненным.)18561857```c1858PyNumberMethods   *tp_as_number;1859PySequenceMethods *tp_as_sequence;1860PyMappingMethods  *tp_as_mapping;1861```18621863Если требуется, чтобы объект мог вести себя как число, последовательность или отображение, нужно поместить адрес структуры, реализующей C-тип [`PyNumberMethods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyNumberMethods), [`PySequenceMethods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PySequenceMethods) или [`PyMappingMethods`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyMappingMethods) соответственно. Заполнять эту структуру подходящими значениями – ваша задача. Примеры использования каждой из них можно найти в каталоге `Objects` дистрибутива исходного кода Python.18641865```c1866hashfunc tp_hash;1867```18681869Эта функция, если вы решите её предоставить, должна возвращать хеш-число для экземпляра вашего типа данных. Вот довольно бессмысленный пример:18701871```c1872static long1873newdatatype_hash(newdatatypeobject *obj)1874{1875    long result;1876    result = obj->obj_UnderlyingDatatypePtr->size;1877    result = result * 3;1878    return result;1879}1880```18811882```c1883ternaryfunc tp_call;1884```18851886Эта функция вызывается, когда экземпляр вашего типа данных «вызывается», например, если `obj1` является экземпляром вашего типа данных и скрипт Python содержит `obj1('hello')`, то вызывается обработчик [`tp_call`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_call).18871888Эта функция принимает три аргумента:188918901. *arg1* – это экземпляр типа данных, который является объектом вызова. Если вызов – `obj1('hello')`, то *arg1* равен `obj1`.18912. *arg2* – это кортеж, содержащий аргументы вызова. Для извлечения аргументов можно использовать [`PyArg_ParseTuple()`](https://python-all.ru/3.5/c-api/arg.html#c.PyArg_ParseTuple).18923. *arg3* – это словарь переданных именованных аргументов. Если он не равен *NULL* и вы поддерживаете именованные аргументы, используйте [`PyArg_ParseTupleAndKeywords()`](https://python-all.ru/3.5/c-api/arg.html#c.PyArg_ParseTupleAndKeywords) для извлечения аргументов. Если вы не хотите поддерживать именованные аргументы и этот параметр не равен *NULL*, возбудите исключение [`TypeError`](https://python-all.ru/3.5/library/exceptions.html#TypeError) с сообщением о том, что именованные аргументы не поддерживаются.18931894Вот бессистемный пример реализации функции call.18951896```c1897/* Реализовать функцию вызова.1898 *    obj1 – экземпляр, принимающий вызов.1899 *    obj2 – кортеж, содержащий аргументы вызова, в данном1900 *         случае 3 строки.1901 */1902static PyObject *1903newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)1904{1905    PyObject *result;1906    char *arg1;1907    char *arg2;1908    char *arg3;19091910    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {1911        return NULL;1912    }1913    result = PyUnicode_FromFormat(1914        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",1915        obj->obj_UnderlyingDatatypePtr->size,1916        arg1, arg2, arg3);1917    return result;1918}1919```19201921```c1922/* Итераторы */1923getiterfunc tp_iter;1924iternextfunc tp_iternext;1925```19261927Эти функции обеспечивают поддержку протокола итератора. Любой объект, который хочет поддерживать итерацию по своему содержимому (которое может генерироваться во время итерации), должен реализовать обработчик `tp_iter`. Объекты, возвращаемые обработчиком `tp_iter`, должны реализовывать оба обработчика `tp_iter` и `tp_iternext`. Оба обработчика принимают ровно один параметр – экземпляр, для которого они вызываются, и возвращают новую ссылку. В случае ошибки они должны установить исключение и вернуть *NULL*.19281929Для объекта, представляющего итерируемую коллекцию, обработчик `tp_iter` должен возвращать объект-итератор. Объект-итератор отвечает за поддержание состояния итерации. Для коллекций, которые могут поддерживать несколько итераторов, не мешающих друг другу (как списки и кортежи), должен создаваться и возвращаться новый итератор. Объекты, которые можно итерировать только один раз (обычно из-за побочных эффектов итерации), должны реализовывать этот обработчик, возвращая новую ссылку на себя, и также должны реализовывать обработчик `tp_iternext`. Файловые объекты являются примером такого итератора.19301931Объекты-итераторы должны реализовывать оба обработчика. Обработчик `tp_iter` должен возвращать новую ссылку на итератор (это то же самое, что и обработчик `tp_iter` для объектов, которые можно итерировать только деструктивно). Обработчик `tp_iternext` должен возвращать новую ссылку на следующий объект в итерации, если он есть. Если итерация достигла конца, он может вернуть *NULL* без установки исключения или может установить [`StopIteration`](https://python-all.ru/3.5/library/exceptions.html#StopIteration); отказ от исключения может дать несколько лучшую производительность. Если возникает реальная ошибка, он должен установить исключение и вернуть *NULL*.19321933### 2.2.6. Поддержка слабых ссылок19341935Одна из целей реализации слабых ссылок в Python – позволить любому типу участвовать в механизме слабых ссылок без дополнительных накладных расходов для тех объектов, которым слабые ссылки не нужны (например, числа).19361937Чтобы объект мог быть слабо ссылаемым, расширение должно включить поле [`PyObject*`](https://python-all.ru/3.5/c-api/structures.html#c.PyObject) в структуру экземпляра для использования механизмом слабых ссылок; оно должно быть инициализировано значением *NULL* конструктором объекта. Также необходимо установить поле [`tp_weaklistoffset`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_weaklistoffset) соответствующего объекта типа на смещение этого поля. Например, тип экземпляра определяется следующей структурой:19381939```c1940typedef struct {1941    PyObject_HEAD1942    PyClassObject *in_class;       /* Объект класса */1943    PyObject      *in_dict;        /* Словарь */1944    PyObject      *in_weakreflist; /* Список слабых ссылок */1945} PyInstanceObject;1946```19471948Статически объявленный объект типа для экземпляров определяется следующим образом:19491950```c1951PyTypeObject PyInstance_Type = {1952    PyVarObject_HEAD_INIT(&PyType_Type, 0)1953    0,1954    "module.instance",19551956    /* Многое опущено для краткости... */19571958    Py_TPFLAGS_DEFAULT,                         /* tp_flags */1959    0,                                          /* tp_doc */1960    0,                                          /* tp_traverse */1961    0,                                          /* tp_clear */1962    0,                                          /* tp_richcompare */1963    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */1964};1965```19661967Конструктор типа отвечает за инициализацию списка слабых ссылок значением *NULL*:19681969```c1970static PyObject *1971instance_new() {1972    /* Прочие детали инициализации опущены для краткости */19731974    self->in_weakreflist = NULL;19751976    return (PyObject *) self;1977}1978```19791980Единственное дополнение заключается в том, что деструктор должен вызывать менеджер слабых ссылок для очистки любых слабых ссылок. Это требуется только в том случае, если список слабых ссылок не равен *NULL*:19811982```c1983static void1984instance_dealloc(PyInstanceObject *inst)1985{1986    /* Выделить временные объекты, если нужно, но не начинать1987       уничтожение пока не начинать1988     */19891990    if (inst->in_weakreflist != NULL)1991        PyObject_ClearWeakRefs((PyObject *) inst);19921993    /* Продолжить уничтожение объекта обычным образом. */1994}1995```19961997### 2.2.7. Дополнительные рекомендации19981999Помните, что большинство этих функций можно опустить, в этом случае вы передаёте `0` в качестве значения. Для каждой функции, которую необходимо предоставить, существуют определения типов. Они находятся в `object.h` в каталоге include Python, который поставляется с исходным дистрибутивом Python.20002001Чтобы узнать, как реализовать определённый метод для нового типа данных, сделайте следующее: загрузите и распакуйте исходный дистрибутив Python. Перейдите в каталог `Objects`, затем найдите в C-файлах исходников `tp_` вместе с нужной функцией (например, `tp_richcompare`). Вы найдёте примеры реализации нужной функции.20022003Когда требуется проверить, является ли объект экземпляром реализуемого типа, используйте функцию [`PyObject_TypeCheck()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_TypeCheck). Пример её использования может выглядеть так:20042005```c2006if (! PyObject_TypeCheck(some_object, &MyType)) {2007    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");2008    return NULL;2009}2010```20112012Сноски20132014| [\[1\]](https://python-all.ru/3.5/extending/newtypes.html#id2) | Это верно, когда известно, что объект относится к базовому типу, например, строке или числу с плавающей запятой. |2015| --- | --- |20162017| [\[2\]](https://python-all.ru/3.5/extending/newtypes.html#id3) | В этом примере мы полагались на это в обработчике [`tp_dealloc`](https://python-all.ru/3.5/c-api/typeobj.html#c.PyTypeObject.tp_dealloc), поскольку наш тип не поддерживает сборку мусора. Даже если тип поддерживает сборку мусора, существуют вызовы для «открепления» объекта от сборки мусора, однако эти вызовы являются продвинутыми и здесь не рассматриваются. |2018| --- | --- |20192020| [\[3\]](https://python-all.ru/3.5/extending/newtypes.html#id4) | Теперь мы знаем, что первый и последний элементы являются строками, поэтому, возможно, мы могли бы быть менее осторожны с уменьшением их счётчиков ссылок, однако мы принимаем экземпляры подклассов строк. Хотя освобождение обычных строк не будет вызывать обратные вызовы в наши объекты, мы не можем гарантировать, что освобождение экземпляра подкласса строки не вызовет обратные вызовы. |2021| --- | --- |20222023| [\[4\]](https://python-all.ru/3.5/extending/newtypes.html#id5) | Даже в третьей версии мы не гарантированы от циклов. Допускаются экземпляры подклассов строк, и подклассы строк могут допускать циклы, даже если обычные строки этого не делают. |2024| --- | --- |2025