2. Определение типов расширений: Учебник¶
Python позволяет автору модуля расширения C определить новые типы, которыми можно манипулировать из кода Python, подобно встроенным типам str
и list
. Код для всех типов расширений строится по определенной схеме, но есть некоторые детали, которые необходимо понять, прежде чем приступать к работе. Этот документ представляет собой легкое введение в тему.
2.1. Основы¶
Время выполнения CPython воспринимает все объекты Python как переменные типа PyObject*, который служит «базовым типом» для всех объектов Python. Сама структура PyObject
содержит только reference count объекта и указатель на «тип объекта». Именно здесь и происходит действие; объект типа определяет, какие функции (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
struct, которая выделяется один раз для каждого экземпляраCustom
.Как ведет себя
Custom
type: этоCustomType
struct, определяющая набор флагов и указателей функций, которые интерпретатор проверяет при запросе определенных операций.Как инициализировать модуль
custom
: это функцияPyInit_custom
и связанная с ней структураcustommodule
.
Первый бит:
typedef struct {
PyObject_HEAD
} CustomObject;
Вот что будет содержать объект Custom. PyObject_HEAD
является обязательным в начале каждой объектной структуры и определяет поле ob_base
типа PyObject
, содержащее указатель на объект типа и счетчик ссылок (доступ к ним можно получить с помощью макросов Py_TYPE
и Py_REFCNT
соответственно). Макросы нужны для того, чтобы абстрагироваться от компоновки и включить дополнительные поля в debug builds.
Примечание
После макроса 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
имеет гораздо больше fields, чем определение выше. Оставшиеся поля будут заполнены нулями компилятором языка Си, и обычно принято не указывать их явно, если они вам не нужны.
Мы разберем его на части, по одному полю за раз:
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. Если вам нужны дополнительные члены, вам нужно будет добавить OR в соответствующие флаги.
Мы предоставляем строку doc для типа в 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 довольно неинтересен. У него нет данных и он ничего не делает. Он даже не может быть подклассифицирован.
Примечание
Хотя в этой документации представлен стандартный модуль distutils
для создания расширений на языке C, в реальных случаях рекомендуется использовать более новую и лучше поддерживаемую библиотеку setuptools
. Документация о том, как это сделать, выходит за рамки данного документа и может быть найдена в Python Packaging User’s Guide.
2.2. Добавление данных и методов в пример Basic¶
Давайте расширим базовый пример и добавим в него некоторые данные и методы. Также сделаем тип пригодным для использования в качестве базового класса. Мы создадим новый модуль 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>
Этот элемент содержит декларации, которые мы используем для работы с атрибутами, как описано чуть позже.
Тип 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
; или этот деструктор может освободить Global interpreter Lock и позволить произвольному коду выполняться в других потоках, которые получают доступ к нашему объекту и изменяют его.
Чтобы быть параноиком и защитить себя от такой возможности, мы почти всегда переназначаем члены перед уменьшением количества их ссылок. Когда нам не нужно этого делать?
когда мы точно знаем, что количество ссылок больше 1;
когда мы знаем, что деаллокация объекта [1] не освободит GIL и не вызовет никаких обратных вызовов в коде нашего типа;
при уменьшении количества ссылок в обработчике
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
, мы будем использовать пользовательские функции getter и setter. Вот функции для получения и установки атрибута 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;
}
Функции getter передается объект Custom
и «закрытие», которое представляет собой указатель на пустоту. В этом случае закрытие игнорируется. (Закрытие поддерживает расширенное использование, при котором геттеру и сеттеру передаются данные определения. Это может быть использовано, например, для создания единого набора функций getter и setter, которые решают, какой атрибут получить или установить, основываясь на данных в закрытии).
Функции setter передается объект 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 есть cyclic garbage collector (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
в класс flags:
.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
.
Сноски