2. 定义扩展类型:教程¶
python允许C扩展模块的编写者定义可以从python代码操作的新类型,就像内置的 str
和 list
类型。所有扩展类型的代码都遵循一个模式,但是在开始之前,您需要了解一些细节。本文档是对该主题的简明介绍。
2.1. 基础知识¶
这个 CPython 运行时将所有python对象视为类型的变量 PyObject* 作为所有Python对象的“基类型”。这个 PyObject
结构本身只包含对象的 reference count 以及指向对象的“类型对象”的指针。这就是操作所在的位置;类型对象决定了当一个属性被查找到一个对象、一个被调用的方法或它被另一个对象相乘时,解释器调用哪个(c)函数。这些C函数称为“类型方法”。
因此,如果要定义新的扩展类型,则需要创建一个新的类型对象。
这类事情只能用示例来解释,所以这里有一个最小但完整的模块,它定义了一个名为 Custom
在C扩展模块内 custom
:
注解
我们在这里展示的是传统的定义方法 静止的 扩展类型。它应该适合大多数用途。C API还允许使用 PyType_FromSpec()
函数,这在本教程中没有介绍。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyObject_HEAD
/* Type-specific fields go here. */
} CustomObject;
static PyTypeObject CustomType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Custom",
.tp_doc = "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
object 包含:这是CustomObject
结构,每个结构分配一次Custom
实例。如何
Custom
type 行为:这是CustomType
结构,它定义一组标志和函数指针,当请求特定操作时,解释器将检查这些标志和函数指针。如何初始化
custom
模块:这是PyInit_custom
函数和关联的custommodule
结构。
第一位是:
typedef struct {
PyObject_HEAD
} CustomObject;
这是自定义对象将包含的内容。 PyObject_HEAD
在每个对象结构的开头是必需的,并定义一个名为 ob_base
类型的 PyObject
,包含指向类型对象的指针和引用计数(这些可以使用宏访问 Py_REFCNT
和 Py_TYPE
分别)。宏的原因是抽象布局并在调试生成中启用其他字段。
注解
后面没有分号 PyObject_HEAD
宏。小心不小心添加了一个:有些编译器会抱怨。
当然,对象通常存储标准之外的附加数据 PyObject_HEAD
样板文件;例如,下面是标准python float的定义:
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,
};
注解
我们建议使用上面的c99样式指定的初始值设定项,以避免列出所有 PyTypeObject
您不关心的字段,也避免关心字段的声明顺序。
的实际定义 PyTypeObject
在里面 object.h
还有更多 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
仅用于可变大小的对象,否则应为零。
注解
如果您希望您的类型可以从python子类化,并且您的类型具有相同的 tp_basicsize
作为它的基类型,您可能会遇到多重继承的问题。类型的python子类必须首先在其 __bases__
否则它将无法调用您的类型 __new__()
方法,但不获取错误。通过确保类型具有更大的 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
类型,将多个成员填充为适当的默认值,包括 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
在shell上应该生成一个文件 custom.so
在子目录中;移动到该目录并启动python——您应该能够 import custom
和玩自定义对象。
不是很难吧?
当然,当前的自定义类型非常无趣。它没有数据,什么也不做。它甚至不能被分类。
注解
尽管本文档展示了标准 distutils
用于构建C扩展的模块,建议在实际用例中使用更新和更好维护的 setuptools
类库。有关如何执行此操作的文档不在本文档的范围内,可以在 Python Packaging User's Guide .
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 = "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结构中现在有三个数据属性, 第一 , last 和 数 . 这个 第一 和 last 变量是包含名字和姓氏的python字符串。这个 数 属性是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
非默认值的属性。
tp_new
传递正在实例化的类型(不一定 CustomType
,如果子类已实例化)和在调用类型时传递的任何参数,并且预期返回创建的实例。 tp_new
处理程序总是接受位置参数和关键字参数,但它们经常忽略这些参数,将参数处理留给初始值设定项(a.k.a. 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__()
你必须 not 尝试在运行时使用方法解析顺序确定要调用的方法。始终静态地确定要调用的类型,并调用它 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时;
当在
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 = "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。例如,这可以用于允许一组getter和setter函数,这些函数根据闭包中的数据决定要获取或设置的属性。)
setter函数通过 Custom
对象、新值和闭包。新值可能是 NULL
,在这种情况下,将删除属性。在setter中,如果属性被删除或其新值不是字符串,则会引发错误。
我们创建了一个 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 = "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;
}
首先,traversal方法让循环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
实现必须准确命名其参数 参观 和 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假定引用计数不为零,我们需要通过调用 PyObject_GC_UnTrack()
在结算会员之前。这是我们重新实现的DealLocator,使用 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 = "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
示例。
脚注