Python调用C++程序的几种实现方式详解
本文详细解析了Python调用C++模块的多种实现路径,涵盖从原生C API导出到基于Boost.Python的高级封装。内容深入讲解了动态库命名规范、符号可见性处理、类与变参函数的导出技巧,以及Python内置数据结构(如list、tuple)在C++侧的映射与处理,为混合编程提供完整参考。
前言
Python以其高效的开发速度和易用性著称,而C++则以卓越的运行效率见长。两者结合可以优势互补:既可以在Python项目中嵌入C++代码以提升性能,也可以在C++项目中利用Python实现外围逻辑。在这类混合架构中,将C++模块导出为Python可调用的接口是常见需求。本文将系统梳理几种将C++代码转化为Python接口的核心方法,帮助读者掌握相关技术。
原生态导出
由于Python解释器本身由C语言编写,只要C++的数据结构能够被Python识别,理论上即可实现直接调用。以下以test1.cpp为例,展示具体的实现代码。
#include <Python.h>
int Add(int x, int y)
{
return x + y;
}
int Del(int x, int y)
{
return x - y;
}
PyObject* WrappAdd(PyObject* self, PyObject* args)
{
int x, y;
if (!PyArg_ParseTuple(args, "ii", &x, &y))
{
return NULL;
}
return Py_BuildValue("i", Add(x, y));
}
PyObject* WrappDel(PyObject* self, PyObject* args)
{
int x, y;
if (!PyArg_ParseTuple(args, "ii", &x, &y))
{
return NULL;
}
return Py_BuildValue("i", Del(x, y));
}
static PyMethodDef test_methods[] = {
{"Add", WrappAdd, METH_VARARGS, "something"},
{"Del", WrappDel, METH_VARARGS, "something"},
{NULL, NULL}
};
extern "C"
void inittest1()
{
Py_InitModule("test1", test_methods);
}
编译该模块的命令如下:
g++ -fPIC -shared test1.cpp -I/usr/include/python2.6 -o test1.so
编译完成后,启动Python解释器进行功能测试,结果如下:
>>> import test1 >>> test1.Add(1,2) 3
在使用原生方法导出时,需特别注意以下关键点:
- 若生成的动态库文件名为test1,则C++源文件中必须包含名为inittest1的初始化函数。同时,Py_InitModule函数的第一个参数必须严格指定为“test1”,否则Python在导入模块时将因找不到初始化入口而失败。
- 对于C++源文件,inittest1函数必须使用extern "C"进行修饰,以防止C++的名称修饰(Name Mangling)机制改变函数符号;而纯C源文件则无需此修饰。这是因为Python解释器在加载库时,会直接查找形如initxxx的函数符号。C和C++对函数符号的编码规则不同,C++在编码时会包含函数长度及参数类型信息。读者可通过
nm test1.so查看具体的函数符号,并利用c++filt工具根据符号反解出原始函数原型。
通过boost实现
沿用上述示例逻辑,使用Boost.Python库实现test2.cpp,代码如下:
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
using namespace boost::python;
int Add(const int x, const int y)
{
return x + y;
}
int Del(const int x, const int y)
{
return x - y;
}
BOOST_PYTHON_MODULE(test2)
{
def("Add", Add);
def("Del", Del);
}
在BOOST_PYTHON_MODULE宏中,第一个参数指定了导出模块的名称。
编译该模块的命令如下:
g++ test2.cpp -fPIC -shared -o test2.so -I/usr/include/python2.6 -I/usr/local/include -L/usr/local/lib -lboost_python
需要注意的是,编译时必须通过-I和-L参数指定Boost的头文件路径和库文件路径。在本例中,这两个路径分别为/usr/local/include和/usr/local/lib。
此外,还可以利用setup.py脚本辅助导出模块。
#!/usr/bin/env python
from distutils.core import setup
from distutils.extension import Extension
setup(name="PackageName",
ext_modules=[
Extension("test2", ["test2.cpp"],
libraries = ["boost_python"])
])
在Extension对象中,第一个参数指定模块名称,第二个参数指定对应的源文件名。
执行构建命令如下:
python setup.py build
执行后将会生成build目录。找到其中的test2.so文件,并进入其同级目录进行验证,测试结果如下:
>>> import test2 >>> test2.Add(1,2) 3 >>> test2.Del(1,2) -1
导出类
若需导出C++类,test3.cpp的实现代码如下:
#include <boost/python.hpp>
using namespace boost::python;
class Test
{
public:
int Add(const int x, const int y)
{
return x + y;
}
int Del(const int x, const int y)
{
return x - y;
}
};
BOOST_PYTHON_MODULE(test3)
{
class_<Test>("Test")
.def("Add", &Test::Add)
.def("Del", &Test::Del);
}
注意:在BOOST_PYTHON_MODULE中,.def方法的使用风格接近Python语法。上述链式调用等效于以下分离的写法:
class_<Test>("Test").def("Add", &Test::Add);
class_<Test>("Test").def("Del", &Test::Del);
编译该模块的命令如下:
g++ test3.cpp -fPIC -shared -o test3.so -I/usr/include/python2.6 -I/usr/local/include/boost -L/usr/local/lib -lboost_python
测试代码如下:
>>> import test3 >>> test = test3.Test() >>> test.Add(1,2) 3 >>> test.Del(1,2) -1
导出变参函数
针对包含默认参数的函数,test4.cpp的实现代码如下:
#include <boost/python.hpp>
using namespace boost::python;
class Test
{
public:
int Add(const int x, const int y, const int z = 100)
{
return x + y + z;
}
};
int Del(const int x, const int y, const int z = 100)
{
return x - y - z;
}
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(Add_member_overloads, Add, 2, 3)
BOOST_PYTHON_FUNCTION_OVERLOADS(Del_overloads, Del, 2, 3)
BOOST_PYTHON_MODULE(test4)
{
class_<Test>("Test")
.def("Add", &Test::Add, Add_member_overloads(args("x", "y", "z"), "something"));
def("Del", Del, Del_overloads(args("x", "y", "z"), "something"));
}
示例中Add和Del函数均使用了默认参数。其中Del为普通函数,Add为类成员函数。代码中分别调用了不同的重载宏,宏定义的最后两个参数分别指定了函数所需的最少参数个数和最多参数个数。
编译该模块的命令如下:
g++ test4.cpp -fPIC -shared -o test4.so -I/usr/include/python2.6 -I/usr/local/include/boost -L/usr/local/lib -lboost_python
测试代码如下:
>>> import test4 >>> test = test4.Test() >>> print test.Add(1,2) 103 >>> print test.Add(1,2,z=3) 6 >>> print test4.Del(1,2) -1 >>> print test4.Del(1,2,z=3) -1
导出带Python对象的接口
既然目标是导出为Python接口,调用方通常会使用Python特有的数据结构,例如tuple、list和dict。由于原生C API处理这些结构较为繁琐,本文仅记录基于Boost的方法。假设需要实现如下Python函数的功能:
def Square(list_a)
{
return [x * x for x in list_a]
}
即接收一个list作为输入,对其中每个元素计算平方值,并返回包含结果的新list。
具体实现代码如下:
#include <boost/python.hpp>
boost::python::list Square(boost::python::list& data)
{
boost::python::list ret;
for (int i = 0; i < len(data); ++i)
{
ret.append(data[i] * data[i]);
}
return ret;
}
BOOST_PYTHON_MODULE(test5)
{
def("Square", Square);
}
编译该模块的命令如下:
g++ test5.cpp -fPIC -shared -o test5.so -I/usr/include/python2.6 -I/usr/local/include/boost -L/usr/local/lib -lboost_python
测试代码如下:
>>> import test5 >>> test5.Square([1,2,3]) [1, 4, 9]
Boost库实现了boost::python::tuple、boost::python::list和boost::python::dict这几个Python数据类型的C++封装。其使用方法与Python原生语法基本保持一致。具体实现细节可查阅Boost头文件中的boost/python/tuple.hpp及其他对应文件。
另外,一个较为常用的辅助函数是boost::python::make_tuple(),其使用示例如下:
boost::python::tuple(int a, int b, int c)
{
return boost::python::make_tuple(a, b, c);
}
总结
以上内容涵盖了Python调用C++模块的主要技术路径。希望本文的讲解能为读者的学习与工作提供有益参考。如有任何疑问,欢迎留言交流。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













