因需,博主需要编写C/C++程序,在其中调用Python编写的相关函数,故此,整理了C/C++调用Python函数的相关方法
对于Python的版本,提及了常用的 Python2.7 与 Python3.5 版本,其余常用的 3.x 版本的使用方法与 3.5 版本的类似,区别将会在教程中点明
对于调用的Python函数,可以是无参的或有参的,可以是有返回值的或是无返回值的
CodeBlocks 环境配置
该部分借鉴了 C++程序调用Python的函数(简单应用)及Ubuntu16.04下codeblocks的环境配置 一文,对其进行了一定的补充说明
该部分针对Ubuntu系统实现,对于其余系统(包括Windows),差别仅仅是Python相关文件的所在位置不同,可手动搜索其存储位置,再按本教程的配置方法实现
针对Python环境的配置,需要知道Python的相关文件位置,可通过以下命令查找Python的相关文件所在
whereis python
该部分的环境配置是基于某一工程实现,因此需要建立工程,再对其进行设置
在建立的工程名上鼠标右键点击,在弹出的选项中选择 Build options…
而后,在新弹出的窗口中选择 Search directories --> Compiler --> Add --> … ,步骤如图
在文件系统中选择 /usr/include/python3.5 文件夹,其中 python3.5 可以根据需要换成其他版本,但该文件必须是 include 类型文件,选择确认后会有一个 Keep this as a relative path? 问题,选择 是,而后在 Add directory 窗口点击确定,结果如图
同样在 Search directories 中,选择 Linker,同样添加相关文件 /usr/lib/python3.5,与之前相同,文件版本可替换,但 Compiler 与 Linker 选择的版本必须一致
而后,在同一窗口中,选择 Linker settings,在其中的 Link Libiaries 中添加文件 /usr/lib/python3.5/config-3.5m-x86_64-linux-gnu/libpython3.5.so ,注意点与之前一致,确认后结果如图
最后,点击 Project build options 窗口的 确定 按钮,保存环境配置
调用Python函数
该部分教程仅使用了C/C++与Python自设的调用方法,并未使用第三方软件实现,该方法在官网中亦有提及,但官网的描述细节部分不足,直接运行其提供的代码会有报错,仅供参考(官网网址:Python 3.5.9 documentation,其中可选择其他版本的帮助文档)
本教程提供的方法同时适用于Python3与Python2.7版本,版本不同而需要修改的一些细节会在方法中点明
程序文件列表
在CodeBlocks中建立了一个名为 Python3 的工程,下图是该工程中的文件列表,其中 main.cpp 为编写的C++程序, test.py 为Python程序,其余文件为工程自动创建或运行后自动创建
Python程序
文件 test.py 中内容如下,仅包含四个函数,有无参数与有无返回值的区别
C/C++程序
1、头文件部分
需要添加 #include “python3.5/Python.h” 头文件,其中 python3.5 部分可以修改为需要的版本
//C程序头文件
#include <stdio.h>
#include <string.h> //需要用到字符串函数
#include "python3.5/Python.h"
//C++程序头文件
#include <iostream>
#include "python3.5/Python.h"
using namespace std;
2、Python参数类型
该部分为C/C++程序中使用的Python数据
PyObject *pName, *pModule, *pFunc, *pArgs, *pValue;
3、Python调用初始化与资源释放
Python资源的初始化与释放是一对操作
Py_Initialize(); //初始化
……
Py_Finalize(); //释放资源
4、Python模块目录切换
其中 char * path 与 string path 是根据博主自己的模块目录填写
char * cstr_cmd 与 string chdir_cmd 将模块目录加入系统的操作是博主搜索了众多博客,最终找到的可以成功运行的方法
C程序与C++程序在该部分有所差别,C语言中并未提供 string 类型,因此需要使用 char* 类型代替,若不熟练,在该步骤中使用 char* 指针可能会有理解上的偏差,可参考 由strcat函数引发的C语言中数组和指针问题的思考 便于理解
//C程序部分
//将Python工作路径切换到待调用模块所在目录
char *path = "/home/wjh/桌面/Python3";
char *str = "sys.path.insert(0,\"";
char *chdir_cmd = (char*)malloc(strlen(path) + strlen(str) + 1);
strcat(strcpy(chdir_cmd, str), path);
str = "\")";
char *cstr_cmd = (char*)malloc(strlen(chdir_cmd) + strlen(str) + 1);
strcat(strcpy(cstr_cmd, chdir_cmd), str);
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);
//C++程序部分
//将Python工作路径切换到待调用模块所在目录
string path = "/home/wjh/桌面/Python3";
string chdir_cmd = string("sys.path.insert(0,\"") + path + "\")";
const char* cstr_cmd = chdir_cmd.c_str();
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);
5、调用模块
其中 PyUnicode_DecodeFSDefault(“test”) 中的 test 是模块名,即编写Python程序时import之后的名称,本教程中,博主编写了一个Python程序直接调用,因此该程序的模块名即为其文件名
以下代码中,因该教程涉及的模块调用方法较为简单,注释部分的代码等同于以上三行代码,具体的差别可自行查看官方帮助文档,可在其中搜索相关函数查看具体描述
pName = PyUnicode_DecodeFSDefault("test"); //模块名
pModule = PyImport_Import(pName); //调用模块
Py_DECREF(pName);
//pModule = PyImport_ImportModule("test"); //调用模块
以上代码适用于Python3版本,Python2.7版本需要将第一行代码修改为如下函数,其余部分保持不变(包括注释的代码,其效用仍旧等同于修改后的三行代码)
pName = PyString_FromString("test"); //模块名
6、调用函数
其中 pModule 为Python模块,“hello” 为需要调用的函数名
pFunc = PyObject_GetAttrString(pModule, "hello"); //调用函数
7、函数参数设置
PyTuple_New 函数用于设置需要调用的Python函数的参数个数
PyLong_FromLong 函数设置参数值,设置的参数类型为long,若需要设置其他类型的参数,可查阅官方文档获取相关函数
PyTuple_SetItem 函数用于将设置的参数值放入参数元组中,其中 pArgs 为参数元组,pValue 为参数值,中间的数字代表该参数值为第几个参数(从0开始)
pArgs = PyTuple_New(2); //参数个数
pValue = PyLong_FromLong(1); //参数值来源
PyTuple_SetItem(pArgs, 0, pValue); //参数设置
pValue = PyLong_FromLong(2); //参数值来源
PyTuple_SetItem(pArgs, 1, pValue); //参数设置
8、函数执行
函数执行涉及两个函数 PyEval_CallObject 与 PyObject_CallObject ,其中 PyEval_CallObject 用于无参数无返回值的Python函数,其余情况使用 PyObject_CallObject 函数
其中 pFunc 为需要调用的Python函数,NULL 表示无参数,pArgs 表示函数参数,pValue 为函数返回值
PyEval_CallObject(pFunc, NULL); //调用无参数无返回值的Python函数
pValue = PyObject_CallObject(pFunc, NULL); //调用无参数有返回值的Python函数
PyObject_CallObject(pFunc, pArgs); //调用有参数无返回值的Python函数
pValue = PyObject_CallObject(pFunc, pArgs); //调用有参数有返回值的Python函数
9、返回值获取
该部分涉及的函数较多,且Python3与Python2.7版本的函数存有差别,具体的使用需要查阅官方文档提供的函数介绍
以下命令以 long 类型为例,适用于Python的各个版本,其中 pValue 为函数执行的返回值
//PyLong_AsLong(pValue)
printf("%ld\n\n", PyLong_AsLong(pValue));
cout << PyLong_AsLong(pValue) << endl << endl;
10、完整代码
该部分代码包含了以上所有内容,调用了四类不同的Python函数,但需要注意的是,该代码使用 C++语言 实现,适用于 Python3.5,其余语言版本需要对其进行一点修改
#include <iostream>
#include "python3.5/Python.h"
using namespace std;
int main() {
PyObject *pName, *pModule, *pFunc, *pArgs, *pValue;
Py_Initialize(); //初始化
//将Python工作路径切换到待调用模块所在目录
string path = "/home/wjh/桌面/Python3";
string chdir_cmd = string("sys.path.insert(0,\"") + path + "\")";
const char* cstr_cmd = chdir_cmd.c_str();
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);
pName = PyUnicode_DecodeFSDefault("test"); //模块名
pModule = PyImport_Import(pName); //调用模块
Py_DECREF(pName);
//pModule = PyImport_ImportModule("test"); //调用模块
//1 无参数无返回
cout << "1 无参数无返回" << endl;
pFunc = PyObject_GetAttrString(pModule, "hello"); //调用函数
PyEval_CallObject(pFunc, NULL); //调用无参数无返回值的Python函数
cout << endl;
//2 无参数有返回
cout << "2 无参数有返回" << endl;
pFunc = PyObject_GetAttrString(pModule, "add"); //调用函数
pValue = PyObject_CallObject(pFunc, NULL);
cout << PyLong_AsLong(pValue) << endl << endl;
//3 有参数无返回
cout << "3 有参数无返回" << endl;
pFunc = PyObject_GetAttrString(pModule, "sub"); //调用函数
pArgs = PyTuple_New(2); //参数个数
pValue = PyLong_FromLong(1); //参数值来源
PyTuple_SetItem(pArgs, 0, pValue); //参数设置
pValue = PyLong_FromLong(2); //参数值来源
PyTuple_SetItem(pArgs, 1, pValue); //参数设置
PyObject_CallObject(pFunc, pArgs);
cout << endl;
//4 有参数有返回
cout << "4 有参数有返回" << endl;
pFunc = PyObject_GetAttrString(pModule, "Add"); //调用函数
pArgs = PyTuple_New(2); //参数个数
pValue = PyLong_FromLong(1); //参数值来源
PyTuple_SetItem(pArgs, 0, pValue); //参数设置
pValue = PyLong_FromLong(2); //参数值来源
PyTuple_SetItem(pArgs, 1, pValue); //参数设置
pValue = PyObject_CallObject(pFunc, pArgs);
cout << PyLong_AsLong(pValue) << endl << endl;
Py_Finalize();
return 0;
}
执行结果(C与C++程序执行结果完全相同)
11、备注说明
C/C++调用Python函数提供了许多判断某一步骤是否出错的函数,类似Java中try与catch涉及的函数操作,具体部分仍旧可参考官方文档,本教程仅提供常用的一些判断操作
检查初始化是否成功
if (!Py_IsInitialized()) {
return 1;
}
判断模块是否调用成功
if (pModule != NULL) {
//调用成功
} else {
PyErr_Print(); //错误打印到命令行
return 1;
}
判断函数是否调用成功,其中使用了 Py_XDECREF() 解除Python对象的引用,以便回收
if (pFunc && PyCallable_Check(pFunc)) {
//调用成功
} else {
if (PyErr_Occurred())
PyErr_Print(); //错误打印到命令行
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
判断函数是否获得返回值
if (pValue != NULL) {
printf("Result of call: %ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);
} else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
return 1;
}