如何用C++扩展Python?
我成功地用C语言扩展了Python,这要感谢这个方便的骨架模块。不过,我找不到适合C++的模块,而且在尝试修复C++编译这个骨架模块时,我遇到了循环依赖的问题。
我该如何用C++扩展Python呢?
如果可以的话,我不想依赖Boost(或者SWIG或其他库)。依赖关系真让人头疼。最理想的情况是,我能找到一个已经可以用C++编译的骨架文件。
这是我为C++编辑的骨架:
#include <Python.h>
#include "Flp.h"
static PyObject * ErrorObject;
typedef struct {
PyObject_HEAD
PyObject * x_attr; // attributes dictionary
} FlpObject;
static void Flp_dealloc(FlpObject * self);
static PyObject * Flp_getattr(FlpObject * self, char * name);
static int Flp_setattr(FlpObject * self, char * name, PyObject * v);
DL_EXPORT(void) initflp();
static PyTypeObject Flp_Type = {
/* The ob_type field must be initialized in the module init function
* to be portable to Windows without using C++. */
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"Flp", /*tp_name*/
sizeof(FlpObject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)Flp_dealloc, /*tp_dealloc*/
0, /*tp_print*/
(getattrfunc)Flp_getattr, /*tp_getattr*/
(setattrfunc)Flp_setattr, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
};
#define FlpObject_Check(v) ((v)->ob_type == &Flp_Type)
static FlpObject * newFlpObject(PyObject * arg)
{
FlpObject * self;
self = PyObject_NEW(FlpObject, &Flp_Type);
if (self == NULL)
return NULL;
self->x_attr = NULL;
return self;
}
// Flp methods
static void Flp_dealloc(FlpObject * self)
{
Py_XDECREF(self->x_attr);
PyMem_DEL(self);
}
static PyObject * Flp_demo(FlpObject * self, PyObject * args)
{
if (! PyArg_ParseTuple(args, ""))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef Flp_methods[] = {
{"demo", (PyCFunction)Flp_demo, 1},
{NULL, NULL} // sentinel
};
static PyObject * Flp_getattr(FlpObject * self, char * name)
{
if (self->x_attr != NULL) {
PyObject * v = PyDict_GetItemString(self->x_attr, name);
if (v != NULL) {
Py_INCREF(v);
return v;
}
}
return Py_FindMethod(Flp_methods, (PyObject *)self, name);
}
static int Flp_setattr(FlpObject * self, char * name, PyObject * v)
{
if (self->x_attr == NULL) {
self->x_attr = PyDict_New();
if (self->x_attr == NULL)
return -1;
}
if (v == NULL) {
int rv = PyDict_DelItemString(self->x_attr, name);
if (rv < 0)
PyErr_SetString(PyExc_AttributeError,
"delete non-existing Flp attribute");
return rv;
}
else
return PyDict_SetItemString(self->x_attr, name, v);
}
/* --------------------------------------------------------------------- */
/* Function of two integers returning integer */
static PyObject * flp_foo(PyObject * self, PyObject * args)
{
long i, j;
long res;
if (!PyArg_ParseTuple(args, "ll", &i, &j))
return NULL;
res = i+j; /* flpX Do something here */
return PyInt_FromLong(res);
}
/* Function of no arguments returning new Flp object */
static PyObject * flp_new(PyObject * self, PyObject * args)
{
FlpObject *rv;
if (!PyArg_ParseTuple(args, ""))
return NULL;
rv = newFlpObject(args);
if ( rv == NULL )
return NULL;
return (PyObject *)rv;
}
/* Example with subtle bug from extensions manual ("Thin Ice"). */
static PyObject * flp_bug(PyObject * self, PyObject * args)
{
PyObject *list, *item;
if (!PyArg_ParseTuple(args, "O", &list))
return NULL;
item = PyList_GetItem(list, 0);
/* Py_INCREF(item); */
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0);
printf("\n");
/* Py_DECREF(item); */
Py_INCREF(Py_None);
return Py_None;
}
/* Test bad format character */
static PyObject * flp_roj(PyObject * self, PyObject * args)
{
PyObject *a;
long b;
if (!PyArg_ParseTuple(args, "O#", &a, &b))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
/* List of functions defined in the module */
static PyMethodDef flp_methods[] = {
{"roj", flp_roj, 1},
{"foo", flp_foo, 1},
{"new", flp_new, 1},
{"bug", flp_bug, 1},
{NULL, NULL} /* sentinel */
};
/* Initialization function for the module (*must* be called initflp) */
DL_EXPORT(void) initflp()
{
PyObject *m, *d;
/* Initialize the type of the new type object here; doing it here
* is required for portability to Windows without requiring C++. */
Flp_Type.ob_type = &PyType_Type;
/* Create the module and add the functions */
m = Py_InitModule("flp", flp_methods);
/* Add some symbolic constants to the module */
d = PyModule_GetDict(m);
ErrorObject = PyErr_NewException("flp.error", NULL, NULL);
PyDict_SetItemString(d, "error", ErrorObject);
}
这个对我来说编译得很好,但当我测试它时:
$ python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import flp
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: dynamic module does not define init function (initflp)
>>>
3 个回答
那Boost::Python怎么样呢?
补充说明:抱歉,我之前没注意到你不想依赖Boost,但我觉得它可能还是一个不错的选择。
使用 extern C 来包裹所有从 Python 调用的函数名。因为 C++ 编译器会使用一种叫做“名称修饰”的技术(这是为了处理函数重载),所以 Python 不能直接读取 C++ 库。但是使用 extern C 可以解决这个问题。你可以这样做:
// most of your code can go whereever void cpp_function() {} extern "C" { // all functions that python calls directly must go in here void python_function() {} }
一定要确保把 Python 需要的每个函数都放在 extern 块里面。你仍然可以在这些函数中使用 C++ 的特性,只是函数名会以不被“名称修饰”的方式导出。
首先,虽然你可能不想增加额外的依赖,但我建议你看看 PyCXX。在它的网页上有这么一句话:
CXX/Objects 是一套 C++ 的工具,目的是让编写 Python 扩展变得更简单。PyCXX 让你写 Python 扩展更容易的主要原因是,它大大降低了你程序出现引用计数错误的可能性,也不需要你不断检查 Python C API 的错误返回。CXX/Objects 通过以下方式将 Python 和 C++ 结合起来:
- C++ 的异常处理机制用来检测错误和清理。在复杂的函数中,使用 C 语言时这通常是个大问题。而使用 PyCXX,我们让编译器来跟踪在发生错误时需要解除引用的对象。
- 标准模板库(STL)及其众多算法可以与 Python 的容器(比如列表和元组)无缝对接。
- 可选的 CXX/Extensions 功能允许你用对象和方法调用来替代笨重的 C 表,这样可以更好地定义你的模块和扩展对象。
我认为 PyCXX 是在 BSD 许可证下发布的,这意味着如果你的扩展也在类似的许可证下发布,你可以直接把 PyCXX 的整个源代码包含在你发布的压缩包里。
如果你真的不想依赖 PyCXX 或其他任何第三方库,我觉得你只需要把 Python 解释器调用的函数用 extern "C" {
和 }
包裹起来,这样可以避免名称混淆。
这是修正后的代码:
#include <Python.h>
#include "Flp.h"
static PyObject * ErrorObject;
typedef struct {
PyObject_HEAD
PyObject * x_attr; // attributes dictionary
} FlpObject;
extern "C" {
static void Flp_dealloc(FlpObject * self);
static PyObject * Flp_getattr(FlpObject * self, char * name);
static int Flp_setattr(FlpObject * self, char * name, PyObject * v);
DL_EXPORT(void) initflp();
}
static PyTypeObject Flp_Type = {
/* The ob_type field must be initialized in the module init function
* to be portable to Windows without using C++. */
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"Flp", /*tp_name*/
sizeof(FlpObject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)Flp_dealloc, /*tp_dealloc*/
0, /*tp_print*/
(getattrfunc)Flp_getattr, /*tp_getattr*/
(setattrfunc)Flp_setattr, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
};
#define FlpObject_Check(v) ((v)->ob_type == &Flp_Type)
static FlpObject * newFlpObject(PyObject * arg)
{
FlpObject * self;
self = PyObject_NEW(FlpObject, &Flp_Type);
if (self == NULL)
return NULL;
self->x_attr = NULL;
return self;
}
// Flp methods
static void Flp_dealloc(FlpObject * self)
{
Py_XDECREF(self->x_attr);
PyMem_DEL(self);
}
static PyObject * Flp_demo(FlpObject * self, PyObject * args)
{
if (! PyArg_ParseTuple(args, ""))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef Flp_methods[] = {
{"demo", (PyCFunction)Flp_demo, 1},
{NULL, NULL} // sentinel
};
static PyObject * Flp_getattr(FlpObject * self, char * name)
{
if (self->x_attr != NULL) {
PyObject * v = PyDict_GetItemString(self->x_attr, name);
if (v != NULL) {
Py_INCREF(v);
return v;
}
}
return Py_FindMethod(Flp_methods, (PyObject *)self, name);
}
static int Flp_setattr(FlpObject * self, char * name, PyObject * v)
{
if (self->x_attr == NULL) {
self->x_attr = PyDict_New();
if (self->x_attr == NULL)
return -1;
}
if (v == NULL) {
int rv = PyDict_DelItemString(self->x_attr, name);
if (rv < 0)
PyErr_SetString(PyExc_AttributeError,
"delete non-existing Flp attribute");
return rv;
}
else
return PyDict_SetItemString(self->x_attr, name, v);
}
/* --------------------------------------------------------------------- */
/* Function of two integers returning integer */
static PyObject * flp_foo(PyObject * self, PyObject * args)
{
long i, j;
long res;
if (!PyArg_ParseTuple(args, "ll", &i, &j))
return NULL;
res = i+j; /* flpX Do something here */
return PyInt_FromLong(res);
}
/* Function of no arguments returning new Flp object */
static PyObject * flp_new(PyObject * self, PyObject * args)
{
FlpObject *rv;
if (!PyArg_ParseTuple(args, ""))
return NULL;
rv = newFlpObject(args);
if ( rv == NULL )
return NULL;
return (PyObject *)rv;
}
/* Example with subtle bug from extensions manual ("Thin Ice"). */
static PyObject * flp_bug(PyObject * self, PyObject * args)
{
PyObject *list, *item;
if (!PyArg_ParseTuple(args, "O", &list))
return NULL;
item = PyList_GetItem(list, 0);
/* Py_INCREF(item); */
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0);
printf("\n");
/* Py_DECREF(item); */
Py_INCREF(Py_None);
return Py_None;
}
/* Test bad format character */
static PyObject * flp_roj(PyObject * self, PyObject * args)
{
PyObject *a;
long b;
if (!PyArg_ParseTuple(args, "O#", &a, &b))
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
/* List of functions defined in the module */
static PyMethodDef flp_methods[] = {
{"roj", flp_roj, 1},
{"foo", flp_foo, 1},
{"new", flp_new, 1},
{"bug", flp_bug, 1},
{NULL, NULL} /* sentinel */
};
/* Initialization function for the module (*must* be called initflp) */
DL_EXPORT(void) initflp()
{
PyObject *m, *d;
/* Initialize the type of the new type object here; doing it here
* is required for portability to Windows without requiring C++. */
Flp_Type.ob_type = &PyType_Type;
/* Create the module and add the functions */
m = Py_InitModule("flp", flp_methods);
/* Add some symbolic constants to the module */
d = PyModule_GetDict(m);
ErrorObject = PyErr_NewException("flp.error", NULL, NULL);
PyDict_SetItemString(d, "error", ErrorObject);
}