最近需要在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

具体使用

我们有如下的代码结构

cpp代码转python cpp调用python_Python


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;
}

最后的结果为

cpp代码转python cpp调用python_c++_02

本文的代码在github仓库中的Cpython中,后续会补充一些对神经网络的调用和碰到的Python.h中的API更详细的个人理解