Spec-Zone.ru › Python 3.9

Определение типов расширений: Руководство

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

2.1. Основы

Интерпретатор CPython рассматривает все объекты Python как переменные типа PyObject*, который служит «базовым типом» для всех объектов Python. Структура PyObject сама по себе содержит только счётчик ссылок объекта и указатель на «объект типа» объекта. Здесь и происходит основное действие; объект типа определяет, какие (C-)функции вызываются интерпретатором, например, когда происходит поиск атрибута объекта, вызов метода или умножение объекта на другой объект. Эти C-функции называются «методами типа».

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

Этот процесс лучше всего объяснять на примере, поэтому вот минимальный, но полный, модуль, который определяет новый тип с именем Custom внутри модуля расширения C custom.

Примечание

Здесь показан традиционный способ определения статических типов расширений. Это должно быть достаточно для большинства случаев использования. API C также позволяет определять типы расширений, размещаемые в куче, с помощью функции PyType_FromSpec(), что не рассматривается в этом руководстве.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} CustomObject;

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

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

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

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

  1. Что содержит объект Custom: это структура CustomObject, которая выделяется один раз для каждого экземпляра Custom.
  2. Как ведет себя тип Custom: это структура CustomType, которая определяет набор флагов и указателей на функции, которые интерпретатор проверяет, когда запрашиваются определенные операции.
  3. Как инициализировать модуль custom: это функция PyInit_custom и связанная с ней структура custommodule.

Первая часть:

typedef struct {
    PyObject_HEAD
} CustomObject;

Вот что будет содержать объект Custom. PyObject_HEAD является обязательным в начале каждой структуры объекта и определяет поле ob_base типа PyObject, содержащее указатель на объект типа и счётчик ссылок (к ним можно получить доступ с помощью макросов Py_TYPE и Py_REFCNT соответственно). Причина использования макроса заключается в абстрагировании от макета и возможности добавления дополнительных полей в отладочных сборках.

Примечание

После макроса PyObject_HEAD выше нет точки с запятой. Будьте осторожны, чтобы случайно не добавить её: некоторые компиляторы могут выдать ошибку.

Конечно, объекты обычно хранят дополнительные данные помимо стандартного шаблона PyObject_HEAD; например, вот определение стандартных чисел с плавающей точкой Python:

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

Вторая часть — определение объекта типа.

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

Примечание

Рекомендуется использовать инициализаторы с указанием имени поля в стиле C99 (как показано выше), чтобы не перечислять все поля PyTypeObject, которые вас не интересуют, а также чтобы не учитывать порядок объявления полей.

Фактическое определение PyTypeObject в object.h содержит гораздо больше полей, чем определение выше. Остальные поля будут заполнены нулями компилятором C, и обычно считается хорошей практикой не указывать их явно, если они не нужны.

Давайте разберём его по одному полю:

PyVarObject_HEAD_INIT(NULL, 0)

Эта строка — обязательный шаблон для инициализации поля ob_base вышеупомянутого поля.

.tp_name = "custom.Custom",

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

>>> "" + custom.Custom()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "custom.Custom") to str

Обратите внимание, что имя является полным именем, включающим как имя модуля, так и имя типа внутри модуля. В этом случае модуль — custom, а тип — Custom, поэтому мы устанавливаем имя типа в custom.Custom. Важно использовать фактический импорт имени для совместимости с модулями pydoc и pickle.

.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,

Это необходимо для того, чтобы Python знал, сколько памяти выделять при создании новых экземпляров Custom. tp_itemsize используется только для объектов переменной длины и в противном случае должно быть равно нулю.

Примечание

Если вы хотите, чтобы ваш тип был наследуем из Python, и ваш тип имеет такой же размер tp_basicsize, как и его базовый тип, могут возникнуть проблемы с множественным наследованием. Подкласс Python вашего типа должен перечислять ваш тип первым в __bases__, иначе он не сможет вызвать метод __new__() вашего типа без ошибки. Вы можете избежать этой проблемы, убедившись, что у вашего типа значение tp_basicsize больше, чем у базового типа. В большинстве случаев это будет выполнено, так как либо базовый тип будет object, либо вы добавите члены данных к базовому типу, тем самым увеличив его размер.

Мы устанавливаем флаги класса в Py_TPFLAGS_DEFAULT.

.tp_flags = Py_TPFLAGS_DEFAULT,

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

Мы предоставляем строку документации для типа в tp_doc.

.tp_doc = PyDoc_STR("Custom objects"),

Для активации создания объектов необходимо предоставить обработчик tp_new. Это эквивалент метода Python __new__(), но должен быть указан явно. В этом случае мы можем просто использовать реализацию по умолчанию, предоставляемую функцией API PyType_GenericNew().

.tp_new = PyType_GenericNew,

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

if (PyType_Ready(&CustomType) < 0)
    return;

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

Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
    Py_DECREF(&CustomType);
    Py_DECREF(m);
    return NULL;
}

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

>>> import custom
>>> mycustom = custom.Custom()

Вот и всё! Всё, что осталось, — это его собрать; поместите приведенный выше код в файл с именем custom.c и:

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

в файл под названием setup.py; затем ввод

$ python setup.py build

в оболочке должен создать файл custom.so в подкаталоге; перейдите в этот каталог и запустите Python — вы должны сможете import custom и поэкспериментировать с объектами Custom.

Не так уж и сложно, правда?

Конечно, текущий тип Custom довольно неинтересный. Он не имеет данных и ничего не делает. Он даже не может быть подклассом.

Примечание

Хотя в данном документе используется стандартный модуль distutils для построения расширений C, в реальных приложениях рекомендуется использовать более новую и лучше поддерживаемую библиотеку setuptools. Документация по этому вопросу выходит за рамки данного документа и доступна в Руководстве пользователя по пакетированию Python.

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

Расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным в качестве базового класса. Создадим новый модуль, custom2, который добавит эти возможности:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

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

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) 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
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    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 Custom_members[] = {
    {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
     "last name"},
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    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 Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom2.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom2",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom2(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

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

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

В этой версии модуля есть ряд изменений.

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

#include <structmember.h>

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

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

Структура объекта обновлена соответственно:

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

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

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

который присваивается члену tp_dealloc:

.tp_dealloc = (destructor) Custom_dealloc,

Этот метод сначала очищает счетчики ссылок двух Python-атрибутов. Py_XDECREF() правильно обрабатывает случай, когда его аргумент равен NULL (что может произойти здесь, если tp_new завершился преждевременно). Затем он вызывает член tp_free типа объекта (вычисленного по Py_TYPE(self)) для освобождения памяти объекта. Обратите внимание, что тип объекта может быть не CustomType, так как объект может быть экземпляром подкласса.

Примечание

Явное приведение к типу destructor выше необходимо, потому что мы определили Custom_dealloc для приема аргумента CustomObject *, но указатель функции tp_dealloc ожидает получения аргумента PyObject *. В противном случае компилятор выведет предупреждение. Это полиморфизм объектно-ориентированного программирования на C!

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

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) 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:

.tp_new = Custom_new,

Обработчик tp_new отвечает за создание (в отличие от инициализации) объектов типа. Он экспонируется в Python как метод __new__(). Нет необходимости определять член tp_new, и многие типы расширений просто повторно используют PyType_GenericNew(), как это сделано в первой версии типа Custom выше. В этом случае мы используем обработчик tp_new для инициализации атрибутов first и last до значений, отличных от NULL.

tp_new получает тип, который создаётся (не обязательно CustomType, если создаётся подкласс), и все аргументы, переданные при вызове типа, и ожидается, что он вернёт созданный экземпляр. Обработчики tp_new всегда принимают позиционные и именованные аргументы, но часто игнорируют их, оставляя обработку аргументов методам инициализации (также известным как tp_init на C или __init__ на Python).

Примечание

tp_new не должен вызывать tp_init явно, так как интерпретатор сделает это сам.

Реализация tp_new вызывает слот tp_alloc для выделения памяти:

self = (CustomObject *) type->tp_alloc(type, 0);

Поскольку выделение памяти может завершиться ошибкой, мы должны проверить результат tp_alloc на предмет NULL перед продолжением.

Примечание

Мы не заполняли сами слот tp_alloc. Вместо этого PyType_Ready() заполняет его за нас, унаследовав его от нашего базового класса, который по умолчанию является object. Большинство типов используют стратегию выделения памяти по умолчанию.

Примечание

Если вы создаёте кооперативный метод tp_new (который вызывает базовый тип tp_new или __new__()), вы не должны пытаться определить вызываемый метод с помощью порядка разрешения методов во время выполнения. Всегда статически определяйте, какой тип вы будете вызывать, и вызывайте его tp_new напрямую или через type->tp_base->tp_new. Если вы этого не сделаете, Python-подклассы вашего типа, которые также унаследованы от других Python-определённых классов, могут работать некорректно. (В частности, вы не сможете создавать экземпляры таких подклассов без получения TypeError.)

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

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    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.

.tp_init = (initproc) Custom_init,

Слот tp_init экспонируется в Python как метод __init__(). Он используется для инициализации объекта после его создания. Инициализаторы всегда принимают позиционные и именованные аргументы, и они должны возвращать либо 0 при успехе, или -1 при ошибке.

В отличие от обработчика tp_new, нет гарантии, что tp_init будет вызван вообще (например, модуль pickle по умолчанию не вызывает __init__() для десериализованных экземпляров). Он также может быть вызван несколько раз. Любой может вызвать метод __init__() для наших объектов. По этой причине мы должны быть особенно осторожны при присвоении новых значений атрибутов. Например, мы могли бы искуситься присвоить член first вот так:

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

Но это было бы рискованно. Наш тип не ограничивает тип члена first, поэтому это может быть любой объект. Он может иметь деструктор, вызывающий выполнение кода, который пытается получить доступ к члену first; или этот деструктор может освободить Глобальную блокировку интерпретатора и позволить произвольному коду выполняться в других потоках, которые обращаются к нашему объекту и изменяют его.

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

  • когда мы абсолютно уверены, что счётчик ссылок больше 1;
  • когда мы знаем, что освобождение объекта 1 не выпустит Глобальную блокировку интерпретатора и не вызовет никаких обращений в код нашего типа;
  • при уменьшении счётчика ссылок в обработчике tp_dealloc для типа, который не поддерживает циклический сбор мусора 2.

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

static PyMemberDef Custom_members[] = {
    {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
     "last name"},
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

и поместить определения в слот tp_members:

.tp_members = Custom_members,

Каждое определение члена имеет имя члена, тип, смещение, флаги доступа и строку документации. Подробности см. в разделе Управление атрибутами ниже.

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

Мы определяем единственный метод Custom.name(), который выводит имя объекта как конкатенацию имен first и last.

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    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-функция, которая принимает экземпляр Custom (или подкласс Custom) в качестве первого аргумента. Методы всегда принимают экземпляр в качестве первого аргумента. Методы часто также принимают позиционные и именованные аргументы, но в данном случае мы не принимаем ни тех, ни других, и не нужно принимать кортеж позиционных аргументов или словарь именованных аргументов. Этот метод эквивалентен Python-методу:

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

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

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

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

(обратите внимание, что мы использовали флаг METH_NOARGS, чтобы указать, что метод ожидает только один аргумент self)

и назначить его слоту tp_methods:

.tp_methods = Custom_methods,

Наконец, мы сделаем наш тип пригодным для использования в качестве базового класса для наследования. Мы написали наши методы так, чтобы они не делали предположений о типе создаваемого или используемого объекта, поэтому нам нужно только добавить флаг Py_TPFLAGS_BASETYPE в определение флагов нашего класса:

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

Мы переименовываем PyInit_custom() в PyInit_custom2(), обновляем имя модуля в структуре PyModuleDef и обновляем полное имя класса в структуре PyTypeObject.

Наконец, мы обновим наш файл setup.py, чтобы создать новый модуль:

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

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

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

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

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

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) 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
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", 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 Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

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

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    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;
    }
    tmp = self->first;
    Py_INCREF(value);
    self->first = value;
    Py_DECREF(tmp);
    return 0;
}

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

static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    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;
    }
    tmp = self->last;
    Py_INCREF(value);
    self->last = value;
    Py_DECREF(tmp);
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom3.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom3",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom3(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

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

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Для повышения контроля над атрибутами first и last мы будем использовать пользовательские функции-геттеры и -сеттеры. Вот функции для получения и установки атрибута first.

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

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    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;
    }
    tmp = self->first;
    Py_INCREF(value);
    self->first = value;
    Py_DECREF(tmp);
    return 0;
}

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

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

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

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

и регистрируем его в слоте tp_getset:

.tp_getset = Custom_getsetters,

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

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

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

Мы также должны обновить обработчик tp_init, чтобы разрешить передачу только строк 3:

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", 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() можно преобразовать в вызовы Py_DECREF(). Единственное место, где мы не можем изменить эти вызовы, — это в реализации tp_dealloc, где существует вероятность того, что инициализация этих членов не удалась в tp_new.

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

2.4. Поддержка циклического сбора мусора

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

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

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

Во второй версии примера Custom мы разрешали хранение любых объектов в атрибутах first или last 4. Кроме того, во второй и третьей версиях мы разрешили наследование от Custom, а подклассы могут добавлять произвольные атрибуты. По любой из этих двух причин объекты Custom могут участвовать в циклах:

>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n

Чтобы объект типа Custom мог участвовать в цикле ссылок и быть корректно обнаружен и собран циклическим GC, наш тип Custom должен заполнить два дополнительных слота и включить флаг, который активирует эти слоты:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

static int
Custom_clear(CustomObject *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) 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
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", 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 Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

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

static int
Custom_setfirst(CustomObject *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_INCREF(value);
    Py_CLEAR(self->first);
    self->first = value;
    return 0;
}

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

static int
Custom_setlast(CustomObject *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_INCREF(value);
    Py_CLEAR(self->last);
    self->last = value;
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom4.Custom",
    .tp_doc = PyDoc_STR("Custom objects"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_traverse = (traverseproc) Custom_traverse,
    .tp_clear = (inquiry) Custom_clear,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom4",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom4(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

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

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Во-первых, метод обхода позволяет циклическому GC получать информацию об объектах-подчинённых, которые могут участвовать в циклах:

static int
Custom_traverse(CustomObject *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(), который автоматизирует вызов функций посещения. С помощью Py_VISIT() можно минимизировать объём служебного кода в Custom_traverse:

static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

Примечание

Реализация tp_traverse должна точно называть свои аргументы visit и arg, чтобы использовать Py_VISIT().

Во-вторых, нам нужно предоставить метод для очистки любых объектов-подчинённых, которые могут участвовать в циклах:

static int
Custom_clear(CustomObject *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

Обратите внимание на использование макроса Py_CLEAR(). Это рекомендуемый и безопасный способ очистки атрибутов данных произвольных типов при уменьшении их счётчиков ссылок. Если вы вызовете Py_XDECREF() вместо этого на атрибуте перед установкой его в значение NULL, существует возможность, что деструктор атрибута вызовет код, который снова обратится к атрибуту (особенно если имеется цикл ссылок).

Примечание

Вы могли бы эмулировать Py_CLEAR(), написав:

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

Тем не менее, намного проще и менее подвержено ошибкам всегда использовать Py_CLEAR() при удалении атрибута. Не пытайтесь микрооптимизировать за счёт надёжности!

Деаллокатор Custom_dealloc может вызывать произвольный код при очистке атрибутов. Это означает, что циклический GC может быть вызван внутри функции. Поскольку GC предполагает, что счётчик ссылок не равен нулю, нам нужно удалить объект из слежения GC, вызвав PyObject_GC_UnTrack() перед очисткой членов. Вот наша повторно реализованная функция деаллокации с использованием PyObject_GC_UnTrack() и Custom_clear:

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

Наконец, мы добавляем флаг Py_TPFLAGS_HAVE_GC к флагам класса:

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

В сущности, всё. Если бы мы написали пользовательские обработчики tp_alloc или tp_free, нам бы понадобилось изменить их для циклического сбора мусора. Большинство расширений будут использовать автоматически предоставляемые версии.

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

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

В этом примере мы создадим тип SubList , который наследуется от встроенного типа list. Новый тип будет полностью совместим со списками, но будет иметь дополнительный increment() метод, который увеличивает внутренний счётчик:

>>> import sublist
>>> s = sublist.SubList(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#include <Python.h>

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

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

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

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

static PyTypeObject SubListType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "sublist.SubList",
    .tp_doc = PyDoc_STR("SubList objects"),
    .tp_basicsize = sizeof(SubListObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_init = (initproc) SubList_init,
    .tp_methods = SubList_methods,
};

static PyModuleDef sublistmodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "sublist",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_sublist(void)
{
    PyObject *m;
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0)
        return NULL;

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

    Py_INCREF(&SubListType);
    if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
        Py_DECREF(&SubListType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Как вы можете видеть, исходный код очень похож на примеры Custom в предыдущих разделах. Мы рассмотрим основные различия между ними.

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

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

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

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

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

Этот шаблон важен при написании типа с настраиваемыми членами tp_new и tp_dealloc. Обработчик tp_new не должен фактически создавать память для объекта с помощью tp_alloc, а должен позволить базовому классу обработать это, вызвав его собственный tp_new.

Структура PyTypeObject поддерживает tp_base, который указывает на конкретный базовый класс типа. Из-за проблем с компилятором на разных платформах, вы не можете заполнить этот поле напрямую ссылкой на PyList_Type; это должно быть сделано позже в функции инициализации модуля:

PyMODINIT_FUNC
PyInit_sublist(void)
{
    PyObject* m;
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0)
        return NULL;

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

    Py_INCREF(&SubListType);
    if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
        Py_DECREF(&SubListType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Перед вызовом PyType_Ready(), структура типа должна иметь заполненным слот tp_base. При наследовании от существующего типа не нужно заполнять слот tp_alloc функцией PyType_GenericNew() – функция выделения памяти из базового типа будет унаследована.

После этого вызов PyType_Ready() и добавление объекта типа в модуль аналогично примерам базовых Custom типов.

Примечания

1

Это верно, когда известно, что объект является основного типа, например, строкой или числом с плавающей точкой.

2

Мы полагались на это в обработчике tp_dealloc в этом примере, потому что наш тип не поддерживает сборку мусора.

3

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

4

Кроме того, даже с ограниченными атрибутами до экземпляров строк, пользователь может передавать произвольные подклассы str и, следовательно, по-прежнему создавать циклы ссылок.

© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/extending/newtypes_tutorial.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API