python C/C++ 扩展

https://young-py.gitbooks.io/py_extend/content/defining_new_types/the_basics.html

基础

Python运行时将所有Python对象视为PyObject*类型变量。PyObject并不是一个非常庞大复杂的对象——它只是包含了引用计数和一个指向"type_object"对象的指针。这是对象表现行为的地方,当类型对象决定调用哪一个C方法的时候,例如,当在对象上查找一个属性gets或乘以另一个对象。这些C函数被称作"type methods"。

所以,如果你想要定义新的对象类型,你需要创建新的类型对象。

这种事情只能用例子来解释,所以给出一个很小但完整的例子,这个模块定义了新的类型:

#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*/
    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对象需要包含的,即引用计数和指向类型对象的指针。这些字段由PyObject_HEAD宏来定义。使用宏是为了标准化布局并且开启特殊调试字段在调试的时候。注意在PyObject_HEAD宏后面没有跟分号;在宏的定义中已经包含一个了。小心在无意中添加一个,很容易习惯性的造成这个错误,并且你的编译器可能不会报错,但是有些会!(在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编译器用0填充,通常的做法是不需要显示的指定它们除非你需要。

我们重新选择上面的进一步分离:

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 */

Python通过PyObjectNew()来分配多少内存。

注意:如果你想让类型为Python的子类,且你的类型有相同的tp_basicsize作为基本类型,你或许在多继承时遇到问题。你的类型的Python子类将会列第一个列出你的类型在它的__bases__,否则将不会调用你的类型的__new__()方法且不返回错误 。你可以避免这个问题当确保你的类型的tp_basicsize有一个更大的值比它的基类。大多数情况是这样的,因为你的基类是object,否则你将增加数据成员到你的基类,且增加它的大小。

0,                          /* tp_itemsize */

这个用于可变长度的对象例如列表和字符串。目前可以忽略它。

忽略一些我们不提供的类型方法,我们可以设置类标识Py_TPFLAGS_DEFAULT

Py_TPFLAGS_DEFAULT,        /*tp_flags*/

所有类型都应该包含这个常量在它们的标识中。它可以使所有被当前版本的Python成员可用。

我们提供一个文档字符串tp_doc给类型。

"Noddy objects",           /* tp_doc */

现在我们进入类型方法,使得你的对象和其他的不同。我们不会去实现其中的任何一个在这个版本的模块。我们将扩展这个示例后会有更多有趣的特性。

现在,我们希望能做的就是创建新的Noddy对象。要启用对象创建,我们提供tp_new的实现。在这个示例,我们可以使用默认的实现通过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类型,填充一些成员,包括ob_type我们最初设置为NULL。

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文件中;然后敲入下面命令

$ python setup.py build

shell会在子目录中生成noddy.so文件;跳到该目录并启动Python——你应该能够import noddy并且摆弄Noddy对象了。

倒不是太难,不是吗?

当然,当前的Noddy类型还不是很有趣。它没有数据并且不能做任何事情。它甚至不能成为子类。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值