编者荐语

在进行 C/C++ 进行开发的时候,很多非核心的功能,可以使用 Python 进行实现,因此学习在 C/C++ 程序中如何调用 Python 程序也是非常有必要的。实现的方法有许多种,今天给大家介绍一种小编认为的最方便的方法。

文章将会介绍一种通过嵌入 Python 来丰富你的 C/C++ 应用程序的方法(Python/C API),这种方式会使您的应用程序能够在不改变程序原有功能的基础上,使用 Python 编程语言而不是 C/C++ 语言来实现应用程序的某些功能。

这种方式可以用于多种目的,主要目的是允许我们通过用 Python 编写一些脚本来根据需要定制应用程序(某些功能可以更容易地用 Python 编写)。当你嵌入 Python 时,主程序一般情况下与 Python 实现无关,应用程序的某些部分会在需要的时候调用 Python 解释器,运行一些我们编写的 Python 代码。所以,如果你需要嵌入 Python,那么你首先需要一个 C/C++ 主程序。

基本使用方法

Python 提供了一套 C API库,使得开发者能很方便地从C/ C++ 程序中调用 Python 模块,C++ 用户应该注意,尽管 API 是完全使用 C 来定义的,但头文件已将入口点声明为 extern “C”,因此 API 在 C++ 中使用此 API 不必再做任何特殊处理。

Python能直接调度jupyter脚本吗 怎么调用python脚本_Python


如果需要使用这个套API,我们需要引入一个头文件和一个库文件,以Cmake构建为例,我们需要在 CMakeLists.txt 中加入:

find_package (Python COMPONENTS Interpreter Development REQUIRED)
target_include_directories(${PROJECT_NAME} PRIVATE ${Python_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE ${Python_LIBRARIES})

Windows环境下应该是需要保证如下两个环境变量的存在(暂未测试)。

Python能直接调度jupyter脚本吗 怎么调用python脚本_API_02


然后我们就可以在 main.cpp 中引入相应的头文件了:

#include <Python.h>

之后,主程序要做的一件事就是是初始化 Python 解释器:

Py_Initialize();

当然,既然需要初始化解释器,作为可以在 C 语言中编写的代码,我们一般还需要对它进行释放(在不需要的时候):

Py_Finalize();

现在我们变可以将需要运行的 Python 语句以字符串的形式传递给 Python 解释器:

PyRun_SimpleString("print("Hello world!")");

举个简单的栗子

此方法没办法进行数值的传递(暂不考虑转换为字符串传递,毕竟麻烦的同时接收数据也是一个大问题):

1.初始化Python解释器;
2.以字符串的形式传递代码;
3.释放python解释器。

代码如下:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int main(int, char **)
{
    Py_Initialize();                                       // 初始化python解释器
    PyRun_SimpleString("import matplotlib.pyplot as plt"); // 运行python代码
    PyRun_SimpleString("plt.plot([1,2,3,4], [12,3,23,231])");
    PyRun_SimpleString("plt.show()");
    Py_Finalize(); // 释放python解释器
};

结果如下:

Python能直接调度jupyter脚本吗 怎么调用python脚本_API_03


显然,我们成功的在 C++ 程序中调用 Python 并绘制图像并显示了出来。

常用数据类型

在python中有一句话叫做“一切皆对象”,这句话可以结合源码更好的进行理解。
在python里,一切变量、函数、类等,在解释器中执行时,都会在在堆中新建一个对象,并将名字绑定在对象上。

在 C/C++ 中,所有的 Python 类型都被声明为 PyObject ,为了能够操作 Python 的数据,Python提供了各种数据类型和 C 语言数据类型的转换操作:

PyObject *object;  // 创建python的object的指针
Py_DECREF(object); // 销毁object

1.数字与字符串处理
在 Python/C API 中提供了 Py_BuildValue() 函数对数字和字符串进行转换处理,使之变成Python中相应的数据类型。

PyObject* Arg = Py_BuildValue("(i, i)", 1, 2);  //i表示创建int型变量

2.列表操作
在 Python/C API 中提供了 PyList_New() 函数用以创建一个新的 Python 列表。PyList_New() 函数的返回值为所创建的列表。

3.元组操作
在 Python/C API 中提供了 PyTuple_New() 函数,用以创建一个新的 Python 元组。PyTuple_New() 函数返回所创建的元组。

4.字典操作
在 Python/C API 中提供了 PyDict_New() 函数用以创建一个新的字典。PyDict_New() 函数返回所创建的字典。

5. cv::Mat 操作
cv::Mat应该是我们会经常使用的格式了,参考代码如下:

PyObject *cvmat2py(cv::Mat &image)
{
    import_array();
    int row, col;
    col = image.cols; //列宽
    row = image.rows; //行高
    int channel = image.channels();
    int irow = row, icol = col * channel;
    npy_intp Dims[3] = {row, col, channel}; //图像维度信息
    PyObject *pyArray = PyArray_SimpleNewFromData(channel, Dims, NPY_UBYTE, image.data);
    PyObject *ArgArray = PyTuple_New(1);
    PyTuple_SetItem(ArgArray, 0, pyArray);
    return ArgArray;
}


举个稍复杂一点的栗子

此方法可以进行进行数值的传递:

1.初始化Python解释器;
2.从C++到Python转换数据;
3.用转换后的数据做参数调用Python函数;
4.把函数返回值转换为C++数据结构;
5.释放python解释器。

C++代码如下:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <iostream>

int main(int, char **)
{
    // 初始化python解释器
    Py_Initialize();
    if (!Py_IsInitialized())
    {
        return -1;
    }

    // 添加编译后文件的所在路径
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");

    // 要调用的python文件名
    auto pModule = PyImport_ImportModule("common"); // 记得复制到编译后文件的路径下,同时不用加后缀名“.py“
    if (!pModule)
    {
        std::cout << "Can't find your xx.py file.";
        getchar();
        return -1;
    }

    //获取模块中的函数
    auto pFunc = PyObject_GetAttrString(pModule, "add"); //从字符串创建python函数对象

    // 参数类型转换
    PyObject *pArg = Py_BuildValue("ii", 1, 2);

    //调用直接获得的函数,并传递参数
    auto *py_result = PyEval_CallObject(pFunc, pArg);

    int c_result;
    // 将python类型的返回值转换为C类型
    PyArg_Parse(py_result, "i", &c_result);
    std::cout << "return: " << c_result << std::endl;
};

Python代码如下(命名为 common.py):

def add(a, b):
    print('a: ', a)
    print('b: ', b)
    return a + b

结果如下(代码中设置的 python 文件搜索路径为编译后的文件路径):

Python能直接调度jupyter脚本吗 怎么调用python脚本_c++_04