《扩展和嵌入python解释器》定义新类型介绍
2定义新类型
正如上一章提到的,Python允许扩展模块的作者定义可以使用Python代码操作的新类型,就象Python内核中的string和list。
这不困难;所有扩展类型的代码遵从一个模式,但有一些细节,你在开始之前必须明白。
注意:定义新类型在Python 2.2中有非常大的变化(目的是为了更好)。本文档描述了如何为Python 2.2及其以后的版本定义新类型。如果你需要支持以前的Python版本,你应该参考这个文档的以前版本。
2.1基础知识
Python运行时将所有Python对象看作PyObject*类型变量。PyObject不是一个非常宏大的对象—他只包含引用计数和指向’type object’对象的指针。这是对象表现行为的地方;类型对象(type object)确定哪一个C函数get被调用,例如,当在对象上查找一个属性gets或乘以另一个对象。这些C函数称作类型方法(’type methods’)从[].append(这些我们称之为对象方法’object mothods’)识别它们。
所以,如果你想定义一个新对象类型,你需要创建一个新的类型对象。
这个顺序只能用例子来解释,所以,下面是一个小例子,但是是完整的,这个模块定义了一个新的类型:
#include <Python.h> typedef struct { PyObject_HEAD /* Type-specific fields go here. */ } noddy_NoddyObject; static PyTypeObject noddy_NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ ‘noddy.Noddy’, /*tp_name*/ sizeof(noddy_NoddyObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 21 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, /*tp_flags*/ ‘Noddy objects’, /* tp_doc */ }; static PyMethodDef noddy_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy(void) { PyObject* m; noddy_NoddyType.tp_new = PyType_GenericNew; if (PyType_Ready(&noddy_NoddyType) < 0) return; m = Py_InitModule3(‘noddy’, noddy_methods, ‘Example module that creates an extension type.’); Py_INCREF(&noddy_NoddyType); PyModule_AddObject(m, ‘Noddy’, (PyObject *)&noddy_NoddyType); }
现在有一部分必须立刻考虑,
新添加的第一部分是:
typedef struct { PyObject_HEAD } noddy_NoddyObject;
这是Noddy对象应该包含的-在本例中,只不过是每个Python对象包含的,命名的引用计数和指向类型对象(type object)的指针。这是PyObject_HEAD宏定义的。使用宏的理由是标准化布局和在debug构造中使能一些特殊的调试字段。注意在宏后面没有分号;宏定义中已经包含了分号。注意不要一不小心加个分号;保持习惯可以很容易做到这一点。你的编译器也许不忌讳这一点,但有些会(在windows上 MSVC认为这是错误,编译不通过)。
对于地,让我看看相应的标准Python整型的定义:
typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;
接着,我们到了关键时刻-类型对象
static PyTypeObject noddy_NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ ‘noddy.Noddy’, /*tp_name*/ sizeof(noddy_NoddyObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*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, /*tp_flags*/ ‘Noddy objects’, /* tp_doc */ };
现在,如果你去object.h文件中查看PyTypeObject的定义,你会看到比上面更多的字段。其余的字段由C编译器用零填充,一般在实际中不明确指定它们,除非你需要它们。
这是很重要的,我们将从这部分的开头开始,然后深入:
PyObject_HEAD_INIT(NULL)
这一行有点累赘,我们更愿意写成这个样子
PyObject_HEAD_INIT(&PyType_Type)
由于类型对象的类型是’type’,但这并不完全符合C和其他编译器的,还好,这个成员可以调用函数PyType_Ready()来给我们填充
0, /* ob_size */
头部的ob_size字段没有使用;
在类型结构中,它的存在是历史原因,即为了和给旧版本Python编译的扩展模块保持二进制兼容。这个字段总是设置为0。
‘noddy.Noddy’, /* tp_name */
我们的类型名。它会出现在我们的对象的缺省文本表示和一些错误信息中,例如:
>>> ‘‘ + noddy.new_noddy()
Traceback (most recent call last):
File ‘<stdin>‘, line 1, in ?
TypeError: cannot add type ‘noddy.Noddy’ to string
注意名称是加点的,包括模块名和类型名。这里模块名是noddy,类型是Noddy,所以我们的类型名是noddy.Noddy。
sizeof(noddy_NoddyObject), /* tp_basicsize */
当你调用PyObject_New(),Python通过它知道需要分配多少内存。
注意:如果你想使你的类型是Python子类,并且你的类型和它的基类型有相同的tp_basicsize,你可能有多重继承问题。你的类型的Python子类必须首先在它的__base__中列出你的类型,否则他不能调用你的类型的__new__方法,而且没有错误。你可以避免这个问题,通过确保你的类型的tp_basicsize值比基类的这个值大。大多数时候,这是真的,因为或者你的基类型是object,或者你给你的基类型增加数据成员,于是,也增加了它的大校
0, /* tp_itemsize */
这和变量长度对象有关,如list和string,这里忽略。
跳过一些我们不提供的类型方法,我门将类标志设置为Py_TPFLAGS_DEFAULT。
Py_TPFLAGS_DEFAULT, /*tp_flags*/
所有的类型应该在它们的标志中包含这个常量。它使能由Python当前版本定义的所有成员。
我们在tp_doc中为类型提供一个文档字符串。
‘Noddy objects’, /* tp_doc */
现在,我们进入类型方法,这使你的对象与其他对象不同。在模块的这个版本中,我们不会去实现任何方法。我们会在后面扩展这个例子,使其有更多有趣的行为。
现在,我们想要能创建新的Noddy对象。使对象可以创建,我们必须提供tp_new的实现。本例中,我们仅能够使用Python API PyType_GenericNew()提供的缺省实现。我们会想只需把它指定到tp_new位置,但我们这样做,为移植性考虑,在一些平台或编译器上,我们不能反对从另一个C模块定义的函数初始化一个结构的成员,所以,使用另一种方法,我们在模块初始化函数中,在调用PyType_Ready()之前,指定tp_new位置。
noddy_NoddyType.tp_new = PyType_GenericNew;
if (PyType_Ready(&noddy_NoddyType) < 0)
return;
所有其他的方法是NULL,我们后面详细讨论它们—在后面的章节中。
在这个文件中,除了initnoddy()函数中的代码,其他的都应该熟悉了:
if (PyType_Ready(&noddy_NoddyType) < 0)
return;
这是初始化Noddy类型,填写一些成员,包括我们初始化设置为NULL的bo_type成员。
PyModule_AddObject(m, ‘Noddy’, (PyObject *)&noddy_NoddyType);
它添加类型到模块字典,这就允许我们调用Noddy类创建Noddy实例:
>>> import noddy
>>> mynoddy = noddy.Noddy()
这就好啦!剩下来所有的事情就是构造它,把上面的代码放到noddy.c文件并把下面代码
from distutils.core import setup, Extension
setup(name=‘noddy’, version=‘1.0’,
ext_modules=[Extension(‘noddy’, [‘noddy.c’])])
放到‘setup.py’文件,然后在shell键入
$ python setup.py build
应该在子目录产生一个文件’noddy.so’;进入到Python路径启动Python—你应该可以import noddy并且操作Noddy对象了。
这不是很难,是吗?
当然,现在的Noddy类型没什么意思。它没有数据并且什么也不做。它甚至不能派生子类。
2.1.1给基础例子增加数据和方法
让我们扩展基础例子添加一些数据和方法。也使这个类型可以用做基类。我们将创建一个新的模块noddy2增加这些能力。
#include <Python.h> #include ‘structmember.h’ typedef struct { PyObject_HEAD PyObject *first; PyObject *last; int number; } Noddy; static void Noddy_dealloc(Noddy* self) { Py_XDECREF(self->first); Py_XDECREF(self->last); self->ob_type->tp_free((PyObject*)self); } static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Noddy *self; self = (Noddy *)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; } static int Noddy_init(Noddy *self, 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) { 26 Chapter 2. Defining New Types tmp = self->last; Py_INCREF(last); self->last = last; Py_XDECREF(tmp); } return 0; } static PyMemberDef Noddy_members[] = { {‘first’, T_OBJECT_EX, offsetof(Noddy, first), 0, ‘first name’}, {‘last’, T_OBJECT_EX, offsetof(Noddy, last), 0, ‘last name’}, {‘number’, T_INT, offsetof(Noddy, number), 0, ‘noddy number’}, {NULL} /* Sentinel */ }; static PyObject * Noddy_name(Noddy* 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 */ }; static PyTypeObject NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ ‘noddy.Noddy’, /*tp_name*/ sizeof(Noddy), /*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 */ Noddy_members, /* 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 */ }; static PyMethodDef module_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy2(void) { PyObject* m; if (PyType_Ready(&NoddyType) < 0) return; m = Py_InitModule3(‘noddy2’, module_methods, ‘Example module that creates an extension type.’); 28 Chapter 2. Defining New Types if (m == NULL) return; Py_INCREF(&NoddyType); PyModule_AddObject(m, ‘Noddy’, (PyObject *)&NoddyType); }
模块的这个版本有些变化。
我们添加了一些额外的东西:
#include ‘structmember.h’
包含这个文件提供了我们用来处理属性的声明,后面将有部分描述。
Noddy对象结构名称截短,写为Noddy,类型对象名截短为NoddyType.
Noddy类型现在有三个数据属性,first,last和number。first和last变量是Python字符串类型,包括first和last名字。number属性是一个整数。
对象结构更新依据:
typedef struct { PyObject_HEAD PyObject *first; PyObject *last; int number; } Noddy;
因为我们现在有数据需要处理,所以我们更加关心对象的分配和释放。最小的实现方式,我们需要一个释放方法:
static void Noddy_dealloc(Noddy* self) { Py_XDECREF(self->first); Py_XDECREF(self->last); self->ob_type->tp_free((PyObject*)self); }
这个方法指定在tp_dealloc位置
(destructor)Noddy_dealloc, /*tp_dealloc*/
这个方法减少两个Python属性的引用计数。我们在这里使用Py_XDECREF()因为first和last可能是NULL。然后调用对象类型的tp_free成员释放对象的内存。注意对象类型也许不是NoddyType,因为对象也许是一个子类的实例。
我们需要确保first和last的名字初始化为空字符串,于是我们提供新的方法:
static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Noddy *self; self = (Noddy *)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; }
并把方法放置在tp_new位置:
Noddy_new, /* tp_new */
这个新成员负责创建()类型对象。在Python中显示为__new__()方法。关于__new__()方法的讨论请参考’Unifying types and classes in Python’文章。实现这个新方法的一个原因是保证实例变量的初始值。在这个例子中,我们不关心初始值是否是NULL,我们使用PyType_GenericNew()作为我们的新方法,就象我们前面做的,PyType_GenericNew初始化所有的实例变量为NULL。
新方法是静态方法,传递已经初始化的类型,当类型被调用时可以传递任何参数。并返回新创建的对象。新方法总是接受位置和关键字参数,但经常忽略这些参数,把这些参数留给初始化方法处理。注意:如果类型支持定义子类,传递的类型可能不是定义的类型。新方法调用tp_alloc设置申请内存。我们不需要自己填写tp_alloc位置。而由从我们基类方法PyType_Ready为我们填充,对象缺省就是这样的。大多数类型使用缺省的分配方式。
注意:如果你创建一个’合作’的tp_new(调用基类类型的tp_new 或__new__),你一定不要试图
。总是静态地确定你要去调用什么类型,并直接调用它的tp_new,或经由type->tp_base->tp_new。如果你不这么做,从其他Python-define类继承下来,并且是你的类型的Python子类,可能无法正确工作(特别地,你不可能创建一个这样的子类的实例而没有产生TypeError错误)。
我们提供一个初始化函数:
static int Noddy_init(Noddy *self, 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; }
填充tp_init位置。
(initproc)Noddy_init, /* tp_init */
tp_init位置的设置在Python表示为__init__()方法。用于在对象创建后初始化对象。不象new方法,我们不能保证’初始化者’(initializer)被调用。当unpickling一个对象时初始化者不能被调用,但它可以被重写。我们的初始化者接受参数给我们的实例提供初始化值。初始化者总是接受位置和关键字参数。
初始化者可以被调用多次,任何人都可以调用我们对象的__init__()方法。因此我们必须在指定新值时格外仔细。例如,我们也许被企图象这样指定first成员:
if (first) { Py_XDECREF(self->first); Py_INCREF(first); self->first = first; } 但
这样做很危险。我们的类型不限制first成员的类型,所以它可以是任何种类的对象。它有析构函数,导致执行试图访问first成员的代码。我们太多疑了,为了保护我们自己避免这种可能性,我们总是在减少它们的引用计数前重新指定成员。那么,我们什么时候做这件事呢?
当我们绝对知道引用计数大于1时
当我们知道释放对象时不会导致任何调用回到我们对象的代码
当在tp_dealloc处理中减少引用计数时
不支持垃圾收集(garbage-collections)
我们想已属性方式暴露我们的实例变量。有一些方法可以做到这一点。最简单的办法时定义成员:
static PyMemberDef Noddy_members[] = { {‘first’, T_OBJECT_EX, offsetof(Noddy, first), 0, ‘first name’}, {‘last’, T_OBJECT_EX, offsetof(Noddy, last), 0, ‘last name’}, {‘number’, T_INT, offsetof(Noddy, number), 0, ‘noddy number’}, {NULL} /* Sentinel */ };
并把定义放到tp_members 位置:
Noddy_members, /* tp_members */
每个定义有成员名,类型,偏移,访问标志和文档字符串。详情请参见后面’Generic Attribute Management’。部分。
这种办法的缺点是不提供限制可以指定给Python属性的对象类型的方法。我们希望first和last的名字是字符串,但任何Python对象都可以指定。并且,属性能够被删除,设置C指针为NULL。即使我们能够确保成员被初始化为非NULL值,如果属性被删除,成员被设置为NULL。
我们定义单个方法,name,输出first和last名字的连接作为对象的名字。
static PyObject * Noddy_name(Noddy* 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; }
这个方法实现一个C函数,带Noddy(或Noddy的子类)实例作为第一个参数。方法总是带一个实例作为第一个参数。方法也经常带位置和关键字参数,但在这个例子中,我们不带任何参数,我们也不需要接受位置参数元组和关键字参数字典。这个方法等同的Python方法:
def name(self):
return ‘%s %s’ % (self.first, self.last)
注意,我们需要检查我们的first和last成员是否可能为NULL。这是因为它们能够被删除,如果被删除,它们被设置为NULL。有更好的方法阻止它们属性的删除和限制属性的值应该是字符串。在后面,我们将看到如何做。
现在,我们已经定义了方法,我们需要创建方法定义数组:
static PyMethodDef Noddy_methods[] = { {‘name’, (PyCFunction)Noddy_name, METH_NOARGS, ‘Return the name, combining the first and last name’ }, {NULL} /* Sentinel */ };
并把它们指定到tp_methods位置。
Noddy_methods, /* tp_methods */
注意我们使用METH_NOARGS标志指示方法需需要传入参数。
最后,我们使我们的类型可以作为基类使用。到目前为止,我们已经仔细地写了我们的方法,它们不需要假设被创建或使用的对象的类型,所以我们所要做的全部就是给我们的类标志增加Py_TPFLAGS_BASETYPE:
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
我们重新命名initnoddy()为initnoddy2(),并更新传递到Py_InitModule3()的模块名称。最后我们更新我们的’setup.py’文件构造新的模块:
from distutils.core import setup, Extension setup(name=‘noddy’, version=‘1.0’, ext_modules=[ Extension(‘noddy’, [‘noddy.c’]), Extension(‘noddy2’, [‘noddy2.c’]), ])
2.1.2为数据属性提供更好的控制
在这部分中,我们将为如何在Noddy例子中设置first和last属性提供更好的控制。在我们模块的前一个版本中,实例变量first和last可以被设置为非字符串值或者甚至被删除。我们想确保这些属性总是被设置为字符串。
#include <Python.h> #include ‘structmember.h’ typedef struct { PyObject_HEAD PyObject *first; PyObject *last; int number; } Noddy; static void Noddy_dealloc(Noddy* self) { Py_XDECREF(self->first); Py_XDECREF(self->last); self->ob_type->tp_free((PyObject*)self); } static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Noddy *self; self = (Noddy *)type->tp_alloc(type, 0); if (self != NULL) { self->first = PyString_FromString(‘‘); 34 Chapter 2. Defining New Types 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; } static int Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) { PyObject *first=NULL, *last=NULL, *tmp; static char *kwlist[] = {‘first’, ‘last’, ‘number’, NULL}; if (! PyArg_ParseTupleAndKeywords(args, kwds, ‘|SSi’, 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 Noddy_members[] = { {‘number’, T_INT, offsetof(Noddy, number), 0, ‘noddy number’}, {NULL} /* Sentinel */ }; static PyObject * Noddy_getfirst(Noddy *self, void *closure) { Py_INCREF(self->first); return self->first; } static int Noddy_setfirst(Noddy *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 *self, void *closure) { Py_INCREF(self->last); return self->last; } static int Noddy_setlast(Noddy *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; } static PyGetSetDef Noddy_getseters[] = { {‘first’, (getter)Noddy_getfirst, (setter)Noddy_setfirst, ‘first name’, NULL}, {‘last’, (getter)Noddy_getlast, (setter)Noddy_setlast, ‘last name’, 36 Chapter 2. Defining New Types NULL}, {NULL} /* Sentinel */ }; static PyObject * Noddy_name(Noddy* self) { static PyObject *format = NULL; PyObject *args, *result; if (format == NULL) { format = PyString_FromString(‘%s %s’); if (format == NULL) 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 */ }; static PyTypeObject NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ ‘noddy.Noddy’, /*tp_name*/ sizeof(Noddy), /*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 */ Noddy_members, /* tp_members */ Noddy_getseters, /* 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 */ }; static PyMethodDef module_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy3(void) { PyObject* m; if (PyType_Ready(&NoddyType) < 0) return; m = Py_InitModule3(‘noddy3’, module_methods, ‘Example module that creates an extension type.’); if (m == NULL) return; Py_INCREF(&NoddyType); PyModule_AddObject(m, ‘Noddy’, (PyObject *)&NoddyType); }
为了给first和last属性提供更好的控制,我们使用了定制的getter和setter函数。下面是获得和设置first属性的函数:
Noddy_getfirst(Noddy *self, void *closure) { Py_INCREF(self->first); return self->first; } static int Noddy_setfirst(Noddy *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; }
getter函数传入一个Noddy对象和一个’closure’,是一个空指针。在这里’closure’被忽略了(closure支持高级应用,传递给setter和getter定义数据。例如,XXXX)。
setter函数传入Noddy对象,新值和’closure’。新值可能是NULL,这种情况下属性被删除。在我们的setter中,如果属性被删除或被指定了一个非字符串值,我们产生(raise)一个错误。
我们创建PyGerSetDef结构数组:
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 */ };
并在tp_getset位置注册:
Noddy_getseters, /* tp_getset */
注册外部的属性getters和setters。
PyGerSetDef结构的最后一项是上面提到的closure。在这里,我们不使用closure,所有我们给它传递NULL。
我们还删除了这些属性的成员定义:
static PyMemberDef Noddy_members[] = { {‘number’, T_INT, offsetof(Noddy, number), 0, ‘noddy number’}, {NULL} /* Sentinel */ };
我们还需要更新tp_init的处理,使其只允许传递字符串:
static int Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) { PyObject *first=NULL, *last=NULL, *tmp; static char *kwlist[] = {‘first’, ‘last’, ‘number’, NULL}; if (! PyArg_ParseTupleAndKeywords(args, kwds, ‘|SSi’, 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值检查。这意味着大部分调用可以改为调用。我们唯一不能改的位置使析构函数(deallocator),因为在构造函数中,成员的初始化可能失败。
我们还重新命名模块初始化函数和初始化函数中的模块名称,跟我们以前做的一样,我们还在’setup.py’文件中增加额外的定义。
2.1.3支持循环垃圾收集
Python有一个循环垃圾收集器(cyclic-garbage collector),能够识别不需要的对象,甚至在对象的引用计数不为0时也可识别。当对象被绕入一个循环中,能够发生这种情况,例如,考虑下面情况:
>>> l = []
>>> l.append(l)
>>> del l
在这个例子中,我们创建一个列表包含它本身。档我们删除它时,它还有一个它本身的引用。它的引用计数不能降为0。幸运的是,Python的cyclic-garbage collector最终计算出列表是垃圾并释放它。
在第二个Noddy例子版本中,我们允许任何类型对象被存储到first和last属性中。这意味着Noddy对象能够参与到垃圾收集循环中:
>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l
这么做相当愚蠢,但给我们一个理由,为Noddy例子增加cyclic-garbage collector支持。为支持循环垃圾收集,类型需要填充两个位置(slot)并设置类标志使能这些位置:
#include <Python.h> #include ‘structmember.h’ typedef struct { PyObject_HEAD PyObject *first; PyObject *last; int number; } Noddy; static int Noddy_traverse(Noddy *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; } static int Noddy_clear(Noddy *self) { PyObject *tmp; tmp = self->first; self->first = NULL; Py_XDECREF(tmp); tmp = self->last; self->last = NULL; Py_XDECREF(tmp); return 0; } static void Noddy_dealloc(Noddy* self) { Noddy_clear(self); self->ob_type->tp_free((PyObject*)self); } static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Noddy *self; self = (Noddy *)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; } static int Noddy_init(Noddy *self, 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 PyMemberDef Noddy_members[] = { {‘first’, T_OBJECT_EX, offsetof(Noddy, first), 0, ‘first name’}, {‘last’, T_OBJECT_EX, offsetof(Noddy, last), 0, ‘last name’}, {‘number’, T_INT, offsetof(Noddy, number), 0, ‘noddy number’}, {NULL} /* Sentinel */ }; static PyObject * Noddy_name(Noddy* 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 */ }; static PyTypeObject NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ ‘noddy.Noddy’, /*tp_name*/ sizeof(Noddy), /*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 | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ ‘Noddy objects’, /* tp_doc */ (traverseproc)Noddy_traverse, /* tp_traverse */ (inquiry)Noddy_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Noddy_methods, /* tp_methods */ Noddy_members, /* 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 */ }; static PyMethodDef module_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy4(void) { PyObject* m; if (PyType_Ready(&NoddyType) < 0) return; m = Py_InitModule3(‘noddy4’, module_methods, ‘Example module that creates an extension type.’); if (m == NULL) return; Py_INCREF(&NoddyType); PyModule_AddObject(m, ‘Noddy’, (PyObject *)&NoddyType); }
traversal方法提供对参与到周期中的子对象的访问:
static int Noddy_traverse(Noddy *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()函数,把对象传递给traversal方法。visit()函数带有传递到traversal方法的子对象参数和额外的参数arg。visit()函数返回值是整数值,一定是非0的整数值。
Python2.4以及更高版本提供Py_VISIT()宏自动调用visit函数。使用Py VISIT()宏,函数Noddy_traverse可以简化为:
static int Noddy_traverse(Noddy *self, visitproc visit, void *arg) { Py_VISIT(self->first); Py_VISIT(self->last); return 0; }
注意:tp_traverse的实现为了使用Py_VISIT()宏,必须明确命名它的参数为visit和arg。这是为了鼓励在这些烦人的实现间保持一致。
为了清除任何参与到周期中的子对象,我们还需要提供一个方法。我们实现了方法,并重新实现了析构函数(dealloctor)使用这个方法:
static int Noddy_clear(Noddy *self) { PyObject *tmp; tmp = self->first; self->first = NULL; Py_XDECREF(tmp); tmp = self->last; self->last = NULL; Py_XDECREF(tmp); return 0; } static void Noddy_dealloc(Noddy* self) { Noddy_clear(self); self->ob_type->tp_free((PyObject*)self); }
注意在Noddy_clear()函数中临时变量的用法。我们使用临时变量,这样我们就能在减少成员的引用计数之前将它设置为NULL。我们这样做,正如我们前面讨论过的,是因为如果引用计数降为0,可能会导致代码运行回调到对象中。另外,因为我们现在支持垃圾收集,我们还需要关心触发垃圾收集的代码。如果垃圾在运行,我们的tp_traverse处理器得到调用。我们不能冒险让Noddy_traverse被调用,而成员的引用计数已经降为0但它的值却没有设置为NULL。
Python 2.4以及更高版本提供Py_CLEAR()可以使小心对待的减少引用计数自动完成。通过使用Py_CLEAR(),Noddy_clear()可以简化为:
static int Noddy_clear(Noddy *self) { Py_CLEAR(self->first); Py_CLEAR(self->last); return 0; }
最后,我们给类标志增加Py_TPFLAGS_HAVE_GC标志:
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
到这就差不多了。如果我们编写定制的tp_alloc和tp_free(放在这个位置的函数,原文slot),我们需要为垃圾收集修改它们。大多数扩展将使用自动提供的版本。
本文地址:http://www.45fan.com/dnjc/71307.html