On this page
2. 定义扩展类型:教程
Python 允许 C 扩展模块的编写者定义可以从 Python 代码操纵的新类型,就像内置的str和list类型一样。所有扩展类型的代码都遵循一种模式,但是在开始之前,您需要了解一些详细信息。本文档是对该主题的简要介绍。
2.1. 基础
CPython运行时将所有 Python 对象视为PyObject*类型的变量,该变量充当所有 Python 对象的“基本类型”。 PyObject结构本身仅包含对象的reference count和指向该对象的“类型对象”的指针。这是行动所在;类型对象确定当(例如)在某个对象上查找某个属性,一种调用的方法或该属性与另一个对象相乘时,解释器将调用哪些(C)函数。这些 C 函数称为“类型方法”。
因此,如果要定义新的扩展类型,则需要创建一个新的类型对象。
这种事情只能pass示例来解释,所以这是一个最小但完整的模块,它在 C 扩展模块custom
内定义了一个名为Custom
的新类型:
Note
我们在这里显示的是定义* static *扩展类型的传统方式。对于大多数用途来说应该足够了。 C API 还允许使用PyType_FromSpec()函数定义堆分配的扩展类型,本教程中没有介绍。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyObject_HEAD
/* Type-specific fields go here. */
} CustomObject;
static PyTypeObject CustomType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Custom",
.tp_doc = "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
type 的行为方式:这是CustomType
结构,它定义了一组标志和函数指针,解释器在请求特定操作时会检查这些标志和函数指针。如何初始化
custom
模块:这是PyInit_custom
函数和关联的custommodule
结构。
第一位是:
typedef struct {
PyObject_HEAD
} CustomObject;
这就是定制对象将包含的内容。 PyObject_HEAD
在每个对象 struct 的开头都是必需的,并定义了一个名为ob_base
的类型PyObject的字段,其中包含指向类型对象的指针和引用计数(可以分别使用宏Py_REFCNT和Py_TYPE进行访问)。宏的原因是要抽象出布局并在调试版本中启用其他字段。
Note
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 = "Custom objects",
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
};
Note
我们建议如上所述使用 C99 样式的指定初始值设定项,以避免列出您不需要的所有PyTypeObject字段,也避免关心字段的语句 Sequences。
object.h
中的PyTypeObject的实际定义比上面的定义多了fields。其余字段将由 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仅用于可变大小的对象,否则应为零。
Note
如果您希望您的类型可以从 Python 继承而来,并且您的类型与其基本类型具有相同的tp_basicsize,则可能存在多重继承问题。您类型的 Python 子类将必须首先在其bases中列出您的类型,否则它将在没有错误的情况下无法调用您类型的new()方法。您可以pass确保您的类型tp_basicsize的值大于其基本类型的值来避免此问题。在大多数情况下,无论如何这都是正确的,因为您的基本类型将为object,否则您将向基本类型添加数据成员,因此会增加其大小。
我们将类标志设置为Py_TPFLAGS_DEFAULT。
.tp_flags = Py_TPFLAGS_DEFAULT,
所有类型都应在其标志中包含此常量。它启用了至少在 Python 3.3 之前定义的所有成员。如果需要其他成员,则需要对相应的标志进行或。
我们为tp_doc中的类型提供一个文档字符串。
.tp_doc = "Custom objects",
要启用对象创建,我们必须提供一个tp_new处理程序。这等效于 Python 方法new(),但必须显式指定。在这种情况下,我们只能使用 API 函数PyType_GenericNew()提供的默认实现。
.tp_new = PyType_GenericNew,
除了PyInit_custom()
中的一些代码,文件中的其他所有内容都应该熟悉:
if (PyType_Ready(&CustomType) < 0)
return;
这将初始化Custom
类型,将许多成员填充为适当的默认值,包括我们最初设置为NULL
的ob_type
。
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
这会将类型添加到模块字典中。这使我们可以pass调用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
的文件中;然后 Importing
$ python setup.py build
在 shell 上应在子目录中产生文件custom.so
;移至该目录并启动 Python-您应该能够import custom
并使用“自定义”对象。
那不是那么难,不是吗?
当然,当前的 Custom 类型非常有趣。它没有数据,什么也没做。它甚至不能被子类化。
Note
尽管本文档展示了用于构建 C 扩展的标准distutils模块,但在实际使用案例中建议使用更新更好的setuptools
库。有关如何执行此操作的文档超出了本文档的范围,可以在Python 打包用户指南中找到。
2.2. 向基本示例添加数据和方法
让我们扩展基本示例以添加一些数据和方法。让我们还将类型用作 Base Class。我们将创建一个新模块custom2
,其中添加了以下Function:
#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 = "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 <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;
因为我们现在有数据要 Management,所以在对象分配和释放方面我们必须更加小心。至少,我们需要一个释放方法:
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
,因为对象可能是子类的实例。
Note
由于我们定义Custom_dealloc
接受CustomObject *
参数,因此需要显式转换为destructor
,但是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
成员,并且实际上许多扩展类型将像上面的Custom
类型的第一个版本那样简单地重用PyType_GenericNew()。在这种情况下,我们使用tp_new
处理程序将first
和last
属性初始化为非NULL
默认值。
传递了tp_new
实例化的类型(如果实例化了一个子类,则不一定是CustomType
)以及调用该类型时传递的任何参数,并且期望该参数返回创建的实例。 tp_new
处理程序始终接受位置和关键字参数,但它们通常会忽略参数,而将参数处理留给初始化方法(在 C 中为tp_init
或在 Python 中为__init__
)。
Note
tp_new
不应显式调用tp_init
,因为解释器会自己执行。
tp_new
实现调用tp_alloc插槽分配内存:
self = (CustomObject *) type->tp_alloc(type, 0);
由于内存分配可能会失败,因此我们必须先针对NULL
检查tp_alloc的结果,然后再 continue。
Note
我们没有自己填写tp_alloc插槽。相反,PyType_Ready()pass从我们的 Base Class(默认是object)继承来为我们填充它。大多数类型使用默认分配策略。
Note
我们还定义了一个初始化函数,该函数接受用于为实例提供初始值的参数:
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;
}
pass填充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
成员的代码。否则该析构函数可以释放GlobalInterpreter 锁并让任意代码在其他访问和修改我们对象的线程中运行。
为了保持偏执并保护自己免受这种可能性的困扰,我们几乎总是在减少其引用计数之前重新分配成员。我们什么时候不必这样做?
当我们完全知道参考计数大于 1 时;
在不支持循环垃圾回收[2]的类型上的tp_dealloc处理程序中减少引用计数时。
我们想将实例变量作为属性公开。有很多方法可以做到这一点。最简单的方法是定义成员定义:
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,
每个成员定义都有一个成员名称,类型,偏移量,访问标志和文档字符串。有关详细信息,请参见下面的通用属性 Management部分。
这种方法的缺点是它没有提供一种方法来限制可以分配给 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
子类)实例作为第一个参数。方法始终将实例作为第一个参数。方法通常也接受位置和关键字参数,但是在这种情况下,我们不接受任何方法,也不需要接受位置参数 Tuples 或关键字参数字典。此方法等效于 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,
最后,我们将使我们的类型可用作子类的 Base Class。到目前为止,我们已经仔细地编写了我们的方法,以使它们对创建或使用的对象的类型不做任何假设,因此我们要做的就是将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. 提供对数据属性的更好控制
在本节中,我们将更好地控制Custom
示例中first
和last
属性的设置方式。在我们模块的先前版本中,实例变量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 = "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
属性的Function:
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 的高级用法.例如,这可用于允许使用一组 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;
}
pass这些更改,我们可以确保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 的循环垃圾收集器finally将发现该列表是垃圾,并将其释放。
在Custom
示例的第二个版本中,我们允许将任何类型的对象存储在first
或last
属性[4]中。此外,在第二版和第三版中,我们允许子类Custom
,并且子类可以添加任意属性。由于以下两个原因中的任何一个,Custom
对象可以参与循环:
>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n
为了允许循环 GC 正确检测和收集参与参考周期的Custom
实例,我们的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 = "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;
}
Note
tp_traverse实现必须使用* visit 和 arg *精确命名其参数,才能使用Py_VISIT()。
其次,我们需要提供一种清除所有可能参与循环的子对象的方法:
static int
Custom_clear(CustomObject *self)
{
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
请注意Py_CLEAR()宏的使用。推荐的一种安全的方法是清除任意类型的数据属性,同时减少其引用计数。如果要在将其设置为NULL
之前在该属性上调用Py_XDECREF(),则该属性的析构函数可能会回调到再次读取该属性的代码中(特别是,如果有参考循环)。
Note
您可以pass编写以下内容来模拟Py_CLEAR():
PyObject *tmp;
tmp = self->first;
self->first = NULL;
Py_XDECREF(tmp);
但是,在删除属性时始终使用Py_CLEAR()会更容易且更容易出错。不要试图以健壮性为代价进行微优化!
清除属性时,解除分配器Custom_dealloc
可以调用任意代码。这意味着可以在函数内部触发循环 GC。由于 GC 假定引用计数不为零,因此我们需要在清除成员之前pass调用PyObject_GC_UnTrack()来从 GC 取消跟踪对象。这是我们使用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结构可能很困难。
在此示例中,我们将创建一个从内置list类型继承的SubList
类型。新类型将与常规列表完全兼容,但将具有附加的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 = "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创建对象的内存,而应让 Base Class pass调用其自己的tp_new处理该内存。
PyTypeObject结构支持tp_base,用于指定类型的具体 Base Class。由于跨平台的编译器问题,您不能直接使用PyList_Type来填充该字段;稍后应在模块初始化Function中完成:
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插槽。在派生现有类型时,不必用PyType_GenericNew()填充tp_alloc插槽-基本类型的分配函数将被继承。
之后,调用PyType_Ready()并将类型对象添加到模块中与基本Custom
示例相同。
Footnotes