Spec-Zone.ru › Python 3.10

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

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

2.1. Основы

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

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

Такое объяснение возможно только на примере, поэтому вот минимальный, но полный, модуль, который определяет новый тип, названный Custom внутри модуля расширения C custom:

Примечание

Здесь показан традиционный способ определения статических типов расширений. Это должно быть достаточно для большинства случаев использования. C API также позволяет определять типы расширений, размещаемых в куче, с помощью функции 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 всегда принимают позиционные и именованные аргументы, но часто игнорируют их, переложив обработку аргументов на методы инициализатора (в C — tp_init, а в Python — __init__).

Примечание

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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/extending/newtypes_tutorial.html

Spec-Zone.ru

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