Определение типов расширений: Руководство
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 = {
.ob_base = 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 = {
.m_base = 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;
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
Сейчас это довольно много информации сразу, но, надеемся, некоторые моменты покажутся знакомыми из предыдущей главы. Этот файл определяет три вещи:
- Что содержит объект
Custom: это структураCustomObject, которая выделяется один раз для каждого экземпляраCustom. - Как ведет себя тип
Custom: это структураCustomType, которая определяет набор флагов и указателей на функции, которые интерпретатор проверяет, когда запрашиваются определенные операции. - Как инициализировать модуль
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 = {
.ob_base = 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, и принято не указывать их явно, если они вам не нужны.
Мы будем разбирать его по одному полю за раз:
.ob_base = 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.
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(m);
return NULL;
}
Это добавляет тип в словарь модуля. Это позволяет создавать экземпляры Custom путём вызова класса Custom:
>>> import custom >>> mycustom = custom.Custom()
Вот и всё! Осталось только скомпилировать его; поместите приведенный выше код в файл с именем custom.c,
[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "custom" version = "1"
в файл с именем pyproject.toml, и
from setuptools import Extension, setup
setup(ext_modules=[Extension("custom", ["custom.c"])])
в файл с именем setup.py; затем наберите в оболочке
$ python -m pip install .
чтобы создать файл custom.so в подкаталоге и установить его; теперь запустите Python — вы должны иметь возможность import custom и поиграться с объектами Custom.
Это не так сложно, не правда ли?
Конечно, текущий тип Custom довольно неинтересный. Он не содержит данных и ничего не делает. Он даже не может быть расширен.
2.2. Добавление данных и методов к базовому примеру
Давайте расширим базовый пример, добавив некоторые данные и методы. Также сделаем тип пригодным в качестве базового класса. Мы создадим новый модуль, custom2 , который добавит эти возможности:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* for offsetof() */
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;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_XSETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_XSETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
{"number", Py_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 = {
.ob_base = 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 = {
.m_base =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;
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
В этой версии модуля есть ряд изменений.
Тип 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!
Мы хотим убедиться, что имя и фамилия инициализированы пустыми строками, поэтому мы предоставляем реализацию 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", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
и поместить определения в слот tp_members:
.tp_members = Custom_members,
Каждое определение члена имеет имя члена, тип, смещение, флаги доступа и строку документации. Подробности см. в разделе Управление общими атрибутами ниже.
Недостатком этого подхода является то, что он не предоставляет способа ограничить типы объектов, которые могут быть присвоены атрибутам Python. Мы ожидаем, что имя и фамилия будут строками, но могут быть присвоены любые объекты Python. Кроме того, атрибуты могут быть удалены, установив указатели C в NULL. Даже если мы можем убедиться, что члены инициализированы значениями, отличными от NULL значениями, члены могут быть установлены в NULL если атрибуты удалены.
Мы определяем один метод, Custom.name(), который выводит имя объекта как конкатенацию имени и фамилии.
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 setuptools import Extension, setup
setup(ext_modules=[
Extension("custom", ["custom.c"]),
Extension("custom2", ["custom2.c"]),
])
а затем переустановим, чтобы мы могли import custom2:
$ python -m pip install .
2.3. Обеспечение более тонкого контроля над атрибутами данных
В этом разделе мы обеспечим более тонкий контроль над тем, как устанавливаются атрибуты first и last в примере Custom. В предыдущей версии нашего модуля переменные экземпляра first и last могли быть установлены на значения, отличные от строк, или даже удалены. Мы хотим убедиться, что эти атрибуты всегда содержат строки.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* for offsetof() */
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;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_SETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_SETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
return Py_NewRef(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_SETREF(self->first, Py_NewRef(value));
return 0;
}
static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
return Py_NewRef(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_SETREF(self->last, Py_NewRef(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 = {
.ob_base = 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 = {
.m_base = 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;
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
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", Py_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 участвовать в цикле ссылок, чтобы его можно было должным образом обнаружить и собрать циклическим сборщиком мусора, наш тип Custom должен заполнить два дополнительных слота и включить флаг, который включает эти слоты:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* for offsetof() */
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;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_SETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_SETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
return Py_NewRef(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_XSETREF(self->first, Py_NewRef(value));
return 0;
}
static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
return Py_NewRef(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_XSETREF(self->last, Py_NewRef(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 = {
.ob_base = 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 = {
.m_base = 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;
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
Во-первых, метод обхода позволяет циклическому сборщику мусора узнать об объектах-подчинённых, которые могут участвовать в циклах:
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 может вызывать произвольный код при очистке атрибутов. Это означает, что циклический сборщик мусора может быть запущен внутри функции. Поскольку сборщик мусора предполагает, что счётчик ссылок не равен нулю, нам нужно исключить объект из учёта сборщиком мусора, вызвав 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;
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
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;
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
Перед вызовом PyType_Ready(), структура типа должна содержать заполненный слот tp_base. При наследовании от существующего типа нет необходимости заполнять слот tp_alloc функцией PyType_GenericNew() – функция выделения памяти будет унаследована от базового типа.
После этого, вызов PyType_Ready() и добавление объекта типа в модуль аналогичны примерам базовых Custom.
Примечания
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/extending/newtypes_tutorial.html