使用C语言编写Python扩展4——创建自定义类型(2)

上一节中我们创建了一个简单的类。这一节我们将对这个类进行扩展,添加属性、方法,并且支持子类。


为类型添加方法和数据


接着上一节的例子,继续编辑noddy.c


typedef struct {
PyObject_HEAD
/ Type-specific fields go here. /
PyObject first; / first name /
PyObject
last; / last name /
int number;
} noddy_NoddyObject;

修改 noddy_NoddyObject 结构体,为其添加三个字段。


然后定义自己的new方法,为对象分配内存空间:


static PyObject  Noddy_new(PyTypeObject type, PyObject args, PyObject kwds)
{
noddy_NoddyObjectself;
self = (noddy_NoddyObject
)type->tp_alloc(type, 0);
if (self != NULL) {
self->first = PyString_FromString(“”);
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
self->last = PyString_FromString(“”);
if (self->last == NULL)
{
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject )self;
}

接着定义对象的初始化函数init


static int Noddy_init(noddy_NoddyObjectself, PyObject args, PyObject kwds)
{
PyObject first=NULL, last=NULL, tmp;
static char
kwlist[] = {“first”, “last”, “number”, NULL};

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 void Noddy_dealloc(noddy_NoddyObject self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject
)self);
}

然后再为该类定义一个方法用于返回该对象的first值和last值:


static PyObject  Noddy_name(noddy_NoddyObject self)
{
static PyObject format = NULL;
PyObject
args, result;
if (format == NULL) {
format = PyString_FromString(“%s %s”);
if (format == NULL)
return NULL;
}
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, “first”);
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, “last”);
return NULL;
}
args = Py_BuildValue(“OO”, self->first, self->last);
if (args == NULL)
return NULL;
result = PyString_Format(format, args);
Py_DECREF(args);
return result;
}
static PyMethodDef Noddy_methods[] = {
{“name”, (PyCFunction)Noddy_name, METH_NOARGS, “Return the name, combining the first and last name”},
{NULL} /
Sentinel /
};

最后在定义 noddy_NoddyType 变量时将对应字段进行填充:


static PyTypeObject noddy_NoddyType = {
PyObject_HEAD_INIT(NULL)
0, /ob_size/
“noddy.Noddy”, /
tp_name/
sizeof(noddy_NoddyObject), /
tp_basicsize/
0, /
tp_itemsize/
(destructor)Noddy_dealloc, /
tp_dealloc/
0, /
tp_print/
0, /
tp_getattr/
0, /
tp_setattr/
0, /
tp_compare/
0, /
tp_repr/
0, /
tp_as_number/
0, /
tp_as_sequence/
0, /
tp_as_mapping/
0, /
tp_hash /
0, /
tp_call/
0, /
tp_str/
0, /
tp_getattro/
0, /
tp_setattro/
0, /
tp_as_buffer/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /
tp_flags/
“Noddy objects”, /
tp_doc/
0, /
tp_traverse /
0, /
tp_clear /
0, /
tp_richcompare /
0, /
tp_weaklistoffset /
0, /
tp_iter /
0, /
tp_iternext /
Noddy_methods, /
tp_methods /
0, /
tp_members /
0, /
tp_getset /
0, /
tp_base /
0, /
tp_dict /
0, /
tp_descr_get /
0, /
tp_descr_set /
0, /
tp_dictoffset /
(initproc)Noddy_init, /
tp_init /
0, /
tp_alloc /
Noddy_new, /
tp_new /
};

tp_flags字段增加了 Py_TPFLAGS_BASETYPE 属性,表示该类可以被继承。最后进行编译测试:


import noddy

o = noddy.Noddy(“abc”, “def”, 12)
print(o, o.name())
print(type(o), type(noddy.Noddy))
print(o.number, o.first, o.last)

class A(noddy.Noddy):
pass

运行以上程序会发现Noddy对象没有number、first和last这几个属性。这是因为虽然在noddy_NoddyObject结构体中定义了这几个字段,但是它们仍然在Python中是不可见的。

为了能在Python中访问这几个属性,需要设置noddy_NoddyType的tp_members字段。


static PyMemberDef Noddy_members[] = {
{“first”, T_OBJECT_EX, offsetof(noddy_NoddyObject, first), 0, “first name”},
{“last”, T_OBJECT_EX, offsetof(noddy_NoddyObject, last), 0, “last name”},
{“number”, T_INT, offsetof(noddy_NoddyObject, number), 0, “noddy number”},
{NULL} / Sentinel /
};

先定义一个 PyMemberDef 结构体类型的数组,然后将noddy_NoddyType的tp_members字段设为Noddy_members。PyMemberDef和T_OBJECT_EX以及T_INT均是在 structmember.h 头文件中定义的,因此还需要先包含该文件。


#include <structmember.h>

数据属性的访问控制


数据属性的访问控制可以对属性的设置进行合法性检查,例如这里我们想要确保 Noddy 对象的first属性和last属性都必须是字符串。

首先定义属性的get方法和set方法:


static PyObject  Noddy_getfirst(noddy_NoddyObject self, void closure)
{
Py_INCREF(self->first);
return self->first;
}
static int Noddy_setfirst(noddy_NoddyObject self, PyObject value, void closure)
{
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, “Cannot delete the first attribute”);
return -1;
}
if (! PyString_Check(value)) {
PyErr_SetString(PyExc_TypeError,
“The first attribute value must be a string”);
return -1;
}
Py_DECREF(self->first);
Py_INCREF(value);
self->first = value;
return 0;
}
static PyObject
Noddy_getlast(noddy_NoddyObject self, void closure)
{
Py_INCREF(self->last);
return self->last;
}
static int Noddy_setlast(noddy_NoddyObject self, PyObject value, void closure)
{
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, “Cannot delete the last attribute”);
return -1;
}
if (! PyString_Check(value)) {
PyErr_SetString(PyExc_TypeError,
“The last attribute value must be a string”);
return -1;
}
Py_DECREF(self->last);
Py_INCREF(value);
self->last = value;
return 0;
}

然后创建一个 PyGetSetDef 结构类型的数组:


static PyGetSetDef Noddy_getseters[] = {
{“first”, (getter)Noddy_getfirst, (setter)Noddy_setfirst, “first name”, NULL},
{“last”, (getter)Noddy_getlast, (setter)Noddy_setlast, “last name”, NULL},
{NULL} / Sentinel */
};

最后再设置 noddy_NoddyType 的tp_getset字段的值为 Noddy_getseters 即可。


注意:以上的代码均是针对Python2的,在Python3中略有不同。


本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。