45fan.com - 路饭网

搜索: 您的位置主页 > 电脑频道 > 电脑教程 > 阅读资讯:《扩展和嵌入python解释器》定义新类型介绍

《扩展和嵌入python解释器》定义新类型介绍

2016-09-02 15:13:04 来源:www.45fan.com 【

《扩展和嵌入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
Tags: 定义 类型 新类型
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部