Определение типов расширений: Руководство
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;
}
Сейчас это довольно много, но, надеюсь, некоторые части покажутся знакомыми из предыдущей главы. Этот файл определяет три вещи:
- Что содержит объект
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 = {
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!
Мы хотим убедиться, что имя и фамилия инициализированы пустыми строками, поэтому мы предоставим реализацию 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-атрибутам. Мы ожидаем, что имена и фамилии будут строками, но можно присвоить любые 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 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.11/extending/newtypes_tutorial.html