在python中调用C/C++的三种方法
作者:哦豁灬
Python 是一种很好用的胶水语言,利用Python的简洁和C++的高效,基本可以解决99%的问题了,剩下那 1% 的问题也就不是问题了,毕竟不是所有问题都可解。
一般的,Python和C++的交互分为这两种情况:
- 用C++扩展Python:当一个Python项目中出现了性能瓶颈时,将瓶颈部分抽离出来,用C++封装成一个Python可以调用的模块(so库);
- 将Python内嵌入C++:当一个C++项目中有部分功能预期将会经常变更需求,期望获得更高的灵活性时,将这部分功能用Python实现,在C++中进行调用。
这里讨论前者,在 python 中调用 C/C++ 代码的方法很多,这里记录三种方法的使用。
1 C/C++ 编译成可执行文件,python 通过 subprocess 调用
C/C++ 代码正常编写,然后编译成 exe/elf 格式的可执行文件,Python 利用 subprocess 调用该可执行文件即可。好处是改动小,不好是至少需要两个进程跑代码,而且 C/C++ 和 Python 通讯比较麻烦。
这种方法简单粗暴,不太好用,没什么好说的。
2 ctypes
C/C++ 在编写代码的时候略微改动,然后编译成 dll/so 格式的动态库文件,Python 利用 ctypes 调用该库文件即可。好处一个进程内运行,C/C++ 侧改动小,坏处是 Python 侧需适配代码比较多。
ctypes 是 python 自带的一个库,可以用来调用 c/cpp 的动态链接库。使用 ctypes 调用 c++ 代码步骤如下:
- 编写 cpp 代码,将其编译成动态链接库(.so 或者 .dll 文件)。
- 在 python 代码文件中导入 ctypes 库,并使用 ctypes.cdll.LoadLibrary() 方法加载动态链接库。
- 使用 ctypes 定义 c++ 函数的参数类型和返回值类型,并调用 c++ 函数。
2.1 编译 C++
一个简单的 demo:
dll.cpp
extern "C" int add(int a, int b) { return a + b; }
在目录 python_call_c_cpp 下,使用 g++ 编译 dll.cpp
g++ --shared -fPIC dll.cpp -o libadd.so
编译完成后,在目录下会生成一个 libadd.so 文件:
2.2 python 调用 C/C++ 库
main.py
import ctypes # 加载动态链接库 lib = ctypes.cdll.LoadLibrary("./libadd.so") # 定义函数参数类型和返回值类型 lib.add.argtypes = [ctypes.c_int, ctypes.c_int] lib.add.restype = ctypes.c_int # 调用 C++ 函数 result = lib.add(1, 2) print("调用C++库的结果:" + str(result))
执行 python3 main.py
:
3 Boost.Python
Boost作为一个大宝库,提供了我们所需要的这一功能。并且,在Boost的许多库中,已经默认使用了Boost.Python,所以也算是经过了充分的测试。
3.1 安装
Boost的大部分功能都是以头文件的形式提供的,无需安装;但是也有少部分功能,需要进行手动编译。Boost.Python 需要进行手动编译。
3.2 一个简单的 demo
用C++实现一个模块,在Python中调用时,可以返回一个特定的字符串。
#include <boost/python.hpp> char const* greet() { return "hello, boost"; } BOOST_PYTHON_MODULE(hello_boostpy) { using namespace boost::python; def("greet", greet); }
将其编译成动态链接库的形式:
g++ -I /usr/include/python2.7/ -fPIC -shared -o hello_boostpy.so http://hello_boostpy.cc -lboost_python
这时可以使用ldd看看hello_boostpy.so可不可以找到libboost_python,找不到的话,需要手动将其路径加入环境变量LD_LIBRARY_PATH中,或者用ldconfig相关的命令也可以。
在Python中使用hello_boostpy库:
# -*- coding: utf-8 -*- import sys sys.path.append('.') def test(): import hello_boostpy return hello_boostpy.greet() if __name__ == "__main__": print test()
接下来,我们在C++实现的模块中,添加一个类,并且尝试向C++方向传入Python的list类型对象。
C++ 类:
#include <boost/python.hpp> #include <vector> #include <string> #include <sstream> using namespace boost::python; struct Person { void set_name(std::string name) { this->name = name; } std::string print_info(); void set_items(list& prices, list& discounts); std::string name; std::vector<double> item_prices; std::vector<double> item_discounts; };
其中,Python方的list类型,在Boost.Python中有一个对应的实现boost::python::list(相应的,dict、tuple等类型都有对应实现)。在set_items中,我们将会用boost::python::extract对数据类型做一个转换。
void Person::set_items(list& prices, list& discounts) { for(int i = 0; i < len(prices); ++i) { double price = extract<double>(prices[i]); double discount = extract<double>(discounts[i]); item_prices.push_back(price); item_discounts.push_back(discount); } }
Python模块定义部分依旧是非常直观的代码:
BOOST_PYTHON_MODULE(person) { class_<Person>("Person") .def("set_name", &Person::set_name) .def("print_info", &Person::print_info) .def("set_items", &Person::set_items) ; }
在Python代码中,就可以像使用一个Python定义的类一样使用Person类了:
# -*- coding: utf-8 -*- import sys sys.path.append('.') def test(): import person p = person.Person() p.set_name('Qie') p.set_items([100, 123.456, 888.8], [0.3, 0.1, 0.5]) print p.print_info() if __name__ == "__main__": test()
附c++调用Python:
将Python安装目录下的include和libs文件夹引入到项目中
将libs目录下的python37.lib复制一份为python37_d.lib
1、Python脚本
def Hello(): print("Hello") def Add(a,b): return a+b
2、C++调用python脚本
#include <Python.h> using namespace std; int main() { Py_Initialize(); //初始化,创建一个Python虚拟环境 if (Py_IsInitialized()) { PyObject* pModule = NULL; PyObject* pFunc = NULL; pModule = PyImport_ImportModule("test_python"); //参数为Python脚本的文件名 if (pModule) { pFunc = PyObject_GetAttrString(pModule, "Hello"); //获取函数 PyEval_CallObject(pFunc, NULL); //执行函数 } else { printf("导入Python模块失败...\n"); } } else { printf("Python环境初始化失败...\n"); } Py_Finalize(); }
接口方法
Python3.6提供给C/C++接口函数,基本都是定义pylifecycle.h,pythonrun.h,ceval.h中。
- Py_Initialize() 和 Py_Finalize()
必须先调用Py_Initialize()进行初始化,这个API用来分配Python解释器使用的全局资源,应用程序结束时需要调用Py_Finalize()来关闭Python的使用环境。 - Py_IsInitialized()
用来判断Python解释器是否初始化成功,true为成功,false为失败。 - PyErr_Print() & PyErr_Clear()
执行Python出错时,PyErr_Print()可将错误信息显示出来,PyErr_Clear()将错误信息在Python解释器的缓存清除。 - PyRun_SimpleString()
这个函数能够用来执行简单的Python语句。 - PyEval_InitThreads()
如果使用多线程调用Python脚本,就需要在初始化Python解释器时调用PyEval_InitThreads()来启用线程支持(导致Python内部启用线程锁),最好在主线程启动时就调用。该API同时也锁定全局解释锁,所以,还需要在初始化完成后需要自行释放锁。
如果不需要使用多线程,不建议启用该选项,互斥锁也会不可避免的增加系统开销。
3、规范化语法
#include<Python.h> //添加python的声明 using namespace std; int main() { Py_Initialize(); //1、初始化python接口 //初始化使用的变量 PyObject* pModule = NULL; PyObject* pFunc = NULL; PyObject* pName = NULL; //2、初始化python系统文件路径,保证可以访问到 .py文件 PyRun_SimpleString("import sys"); PyRun_SimpleString("sys.path.append('./')"); //3、调用python文件名。当前的测试python文件名是test.py。在使用这个函数的时候,只需要写文件的名称就可以了。不用写后缀。 pModule = PyImport_ImportModule("test"); //4、调用函数 pFunc = PyObject_GetAttrString(pModule, "AdditionFc"); //5、给python传参数 PyObject* pArgs = PyTuple_New(2);//函数调用的参数传递均是以元组的形式打包的,2表示参数个数。如果AdditionFc中只有一个参数时,写1就可以了。这里只先介绍函数必须有参数存在的情况。 PyTuple_SetItem(pArgs, 0, Py_BuildValue("i", 2)); //0:表示序号。第一个参数。 PyTuple_SetItem(pArgs, 1, Py_BuildValue("i", 4)); //1:也表示序号。第二个参数。i:表示传入的参数类型是int类型。 //6、使用C++的python接口调用该函数 PyObject* pReturn = PyEval_CallObject(pFunc, pArgs); //7、接收python计算好的返回值 int nResult; PyArg_Parse(pReturn, "i", &nResult);//i表示转换成int型变量。在这里,最需要注意的是:PyArg_Parse的最后一个参数,必须加上“&”符号。 //8、结束python接口初始化 Py_Finalize(); }
总结
到此这篇关于在python中调用C/C++的三种方法的文章就介绍到这了,更多相关python调用C/C++内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!