最近需要在C++中调用一些神经网络相关的东西,开始研究一下怎么在C++中使用python
Python.h
python的开发者已经给出了关于C/C++的接口,为了简洁他们称之为“Python/C API”,与其相关的所有函数、类型和宏定义都包含在了头文件“Python.h”中,我们只需在代码中加入
#include <Python.h>
即可在使用其中的接口在C++中使用python啦
在Ubuntu中,Python.h头文件可以在"usr/include/pythonx.x"中找到,如果你安装了Anaconda,那么你在"${YOUR_ANACONDA_PATH}/anaconda3/include/pythonx.x"中也能找到一个。在使用时我们需要在CmakeLists.txt(使用CMAKE的话)或c_cpp_propertirs.json(使用VScode)中设定,以帮助程序找到Python.h并确定其版本。
CmakeLists.txt
需要加入以下三行
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
include_directories(${Python3_INCLUDE_DIRS})
target_link_libraries(${Python3_LIBRARIES})
如果需要调用python2版本,上面的3全部改成2即可。
我用Python3的时候Cmake自动找到的是Anaconda中的python3而不是系统中的python3,经过测试暂时没有发现怎么通过find_package找到系统自带的python,如果想使用系统的python可以直接指定路径,找到python路径和.so文件路径并设置就好了
SET(Python3_INCLUDE_DIRS "/usr/include/python3.6m")
SET(Python3_LIBRARIES "/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/libpython3.6.so")
message("Python3: " ${Python3_INCLUDE_DIRS})
message("Python3: " ${Python3_LIBRARIES})
c_cpp_propertirs.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
// "/usr/include/python2.7"
// "/usr/include/python3.6"
// "${YOUR_ANACONDA_PATH}/anaconda3/include/python3.9"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "linux-gcc-x64",
"configurationProvider": "ms-vscode.cmake-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4
}
带有//的三行中选一行即可,分别对应系统的python2、python3和anaconda的python3
具体使用
我们有如下的代码结构
hello.py的形式如下
def printHello():
print("Hello world")
def printStr(a, b):
return a+b
有两种调用hello.py的方式
对模块、函数都保存为python中的给出的PyObject类的指针进行使用
void cpython1(){
Py_Initialize(); // 初始化python接口
PyRun_SimpleString("import sys"); // 指定根目录
PyRun_SimpleString("sys.path.append('./')");
PyObject* pModule = PyImport_ImportModule("hello"); // 取hello模块
if( pModule == NULL ){
cout <<"module not found" << endl;
return;
}
PyObject* pFunc = PyObject_GetAttrString(pModule, "printHello"); // 取printHello函数
if( !pFunc || !PyCallable_Check(pFunc)){
cout <<"function not found" << endl;
return;
}
PyEval_CallObject(pFunc, nullptr);
Py_Finalize(); // 结束python接口
}
PyImport_ImportModule和PyObject_GetAttrString的作用顾名思义
利用PyRun_SimpleString直接通过python代码形式的字符串调用python内容
void cpython2(){
Py_Initialize();
PyRun_SimpleString("import sys"); // 通上
PyRun_SimpleString("sys.path.append('./')");
PyRun_SimpleString("import hello"); // 导入模块
PyRun_SimpleString("hello.printHello()"); // 调用函数
Py_Finalize();
}
代用需要参数的函数
上面知道了如何调用函数,现在补充如何给出函数需要的参数块,代码如下
void cpythonWithPra(string& a, string& b){
Py_Initialize();
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyObject* pModule = PyImport_ImportModule("hello");
if(pModule == NULL){
cout << "module not found" << endl;
return;
}
PyObject* pFunc = PyObject_GetAttrString(pModule, "printStr");
if(!pFunc || !PyCallable_Check(pFunc)){
cout << "function not found" << endl;
return;
}
PyObject* pArgs = PyTuple_New(2); // PyTuple_New构造输入的Tuple参数块,指定为2维
PyTuple_SetItem(pArgs, 0, Py_BuildValue("s", a.c_str())); // 对每个参数赋值,通过Py_BuildValue构造
PyTuple_SetItem(pArgs, 1, Py_BuildValue("s", b.c_str()));
PyObject* pReturn = PyEval_CallObject(pFunc, pArgs); // 将参数块传入函数,取得返回值
char* result;
PyArg_Parse(pReturn, "s", &result);
cout << "result: " << result << endl;
Py_Finalize();
}
这里需要说明的是Py_BuildValue()需要两个参数,第一个为数据的类型,第二个为数据,更详细的信息可以在[1][2]中找到。由于Python.h主要是针对C语言的,所以只接受char不接受string类型,所以对字符串a,b需要调用.c_str()同时返回值需要用char类型保存。
总结
main函数如下
int main(){
cout << "cpython1: " << endl;
cpython1();
cout << "cpython2: " << endl;
cpython2();
string a = "c++";
string b = "python";
cpythonWithPra(a, b);
return 0;
}
最后的结果为
本文的代码在github仓库中的Cpython中,后续会补充一些对神经网络的调用和碰到的Python.h中的API更详细的个人理解