C程序中直接调用Python函数(Linux)
前言:开发需要,从GitHub上找了一段使用 keras 深度学习的Python源代码,但主程序是C写的。考虑到数据量太大,每次都运行Python脚本加载深度学习模型过于浪费资源的问题,遂想到能否在C中调用Python函数,只从Python获取一次深度学习的模型,并存放在C的指针中。在每次需要使用模型检验数据时,直接传入已保存的模型指针即可。
一、环境准备(tensorflow深度学习库)
1、安装Python,pip等步骤不再赘述,可自行google。
2、安装tensorflow库:pip3 install tensorflow==1.5.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
3、安装keras库:pip3 install keras==2.0.8 -i https://pypi.tuna.tsinghua.edu.cn/simple
4、安装h5py库:pip3 install h5py==2.10 -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完毕后,可通过pip3 list 查看是否安装成功。
二、C程序中调用Python函数
1、头文件:
#include <Python.h>
若gcc编译是提示找不到库文件,可指定库及头文件路径(通过whereis python3查找):gcc c_test.c -I/usr/include/python3.6m -L/usr/lib/python3.6 -lpython3.6m -o test
2、导入所需要引用的Python函数所在的库:
API: PyObject* pModule = PyImport_ImportModule("Modpy");
Modpy为所要引用的模块名(一般是.py文件名,不需要包含后缀.py),如果导入成功,函数会返回一个指向模块对象的不为空的PyObject*指针(模块指针)。
另:在开发过程中,出现了路径、文件全部正确的情况下,始终导入失败的问题(返回的PyObject*指针始终为NULL)。后来通过加入以下代码,将当前路径加入搜索目录解决:
PyRun_SimpleString( "import sys" );
PyRun_SimpleString( "sys.path.append('.')" );
3、Python库指针获取成功后,获取库中函数指针:
API: PyObject* pFunc = PyObject_GetAttrString(pModule, "FuncName");
pModule为第1步获得的模块指针,FuncName为所要获得函数接口的函数名,如果导入成功,函数会返回一个指向模块对象的不为空的PyObject*指针(函数指针)。
4、将C风格的参数转换为Python风格的参数
API:PyObject *Py_BuildValue(char *format, ...)
此函数将C类型的数据结构转换成Python对象。在下面的描述中,括号中的项是格式单元返回的Python对象类型,方括号中的项为传递的C的值的类型:
“s” (string) [char *] :将C字符串转换成Python对象,如果C字符串为空,返回NONE。
“s#” (string) [char *, int] :将C字符串和它的长度转换成Python对象,如果C字符串为空指针,长度忽略,返回NONE。
“z” (string or None) [char *] :作用同"s"。
“z#” (string or None) [char *, int] :作用同"s#“。
“i” (integer) [int] :将一个C类型的int转换成Python int对象。
“b” (integer) [char] :作用同"i”。
“h” (integer) [short int] :作用同"i"。
“l” (integer) [long int] :将C类型的long转换成Pyhon中的int对象。
“c” (string of length 1) [char] :将C类型的char转换成长度为1的Python字符串对象。
“d” (float) [double] :将C类型的double转换成python中的浮点型对象。
“f” (float) [float] :作用同"d"。
“O&” (object) [converter, anything] :将任何数据类型通过转换函数转换成Python对象,这些数据作为转换函数的参数被调用并且返回一个新的Python对象,如果发生错误返回NULL。
“(items)” (tuple) [matching-items] :将一系列的C值转换成Python元组。
“[items]” (list) [matching-items] :将一系列的C值转换成Python列表。
“{items}” (dictionary) [matching-items] :将一系类的C值转换成Python的字典,每一对连续的C值将转换成一个键值对。
例如:
Py_BuildValue(“”) None
Py_BuildValue(“i”, 123) 123
Py_BuildValue(“iii”, 123, 456, 789) (123, 456, 789)
Py_BuildValue(“s”, “hello”) ‘hello’
Py_BuildValue(“ss”, “hello”, “world”) (‘hello’, ‘world’)
Py_BuildValue(“s#”, “hello”, 4) ‘hell’
Py_BuildValue(“()”) ()
Py_BuildValue(“(i)”, 123) (123,)
Py_BuildValue(“(ii)”, 123, 456) (123, 456)
Py_BuildValue(“(i,i)”, 123, 456) (123, 456)
Py_BuildValue(“[i,i]”, 123, 456) [123, 456] Py_BuildValue(“{s:i,s:i}”, “abc”, 123, “def”, 456) {‘abc’: 123, ‘def’: 456}
Py_BuildValue(“((ii)(ii)) (ii)”, 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
5、构建传入Python函数的参数
在Python/C API中,当需要传入多个参数时,需要将这些参数打包为一个元组,否则会出现错误。除了使用上述第4条所诉的,使用 Py_BuildValue(“(ii)”, 123, 456) 类似的将其转变为(123, 456)数组外,还可使用提供的打包API,将size个对象(可变参数中提供的)打包为一个元组:
PyObject *PyTuple_Pack(Py_ssize_t size, ...)
代码举例:
pDNSName = Py_BuildValue( "s", pcDNSName);//将C的字符串pcDNSName转变为Python变量
pPredictParm = PyTuple_Pack(3, pDNSName, pModel, pCharList);//将构建的DNSName和之前保存的pModel、pCharList指针,构建为python元组以供传入
6、调用Python函数,并传入参数
API:PyObject *PyObject_CallObject(PyObject *callable_object,PyObject *args)
callable_object为步骤3中获得的函数指针,args为步骤5或6中打包的参数元组。返回值为PyObject *,保存了Python函数的返回值。
7、将Python函数返回值转为C程序风格的变量
API:PyArg_Parse( pDNSNameRst, "f", &ret );
pDNSNameRst为步骤6的返回指针。"f"为希望转变的数据类型,此处希望将Python返回的对象转为C的float类型。ret为转为C风格后的保存地址。注意:Python的返回对象,应和要转变的C对象相对应,否则会引发错误。
8、源代码
#include <Python.h>
#include <stdio.h>
int main(){
char* pcDNSName = "sina.com";
Py_Initialize();
if ( !Py_IsInitialized() )
{
return -1;
}
//1. 获取 Binary_xxx 模块指针
PyObject *pMod = NULL; //Binary_xxxn python模块指针
//2. 获取 深度学习模型
PyObject *pFuncLoadmodel = NULL; //c_load_model 函数指针,读取深度学习模型
PyObject* pLoadModelParm = NULL; //c_load_model 函数参数:模型保存的路径
PyObject* pModel = NULL; //保存从python返回的深度学习模型
//3. 获取DNS支持的字符串的深度学习编码列表
PyObject *pFuncLoadcharlist = NULL; //c_load_charlist 函数指针
PyObject* pLoadcharlistParm = NULL; //c_load_charlist 函数参数
PyObject* pCharList = NULL; //保存从python返回的编码列表
//4. 调用 c_predict 函数,测试域名的概率
PyObject *pFuncPredict = NULL; //c_predict 函数指针
PyObject* pDNSName = NULL;
PyObject* pPredictParm = NULL; //c_predict 函数参数
PyObject* pDNSNameRst = NULL; //保存从python返回的测试结果
//5. 解析 c_predict 返回的结果
PyObject* pParse = NULL;
//添加当前路径到搜索目录,没有此段 PyImport_ImportModule 无法搜索到 需引入的库
PyRun_SimpleString( "import sys" );
PyRun_SimpleString( "sys.path.append('.')" );
//1.导入模块Binary_with_attention
pMod = PyImport_ImportModule("Binary_xxx");
if( pMod == NULL ){
printf("ImportModule Binary_with_attention error\n");
return -1;
}
//2.导入函数 c_load_model
pFuncLoadmodel = PyObject_GetAttrString(pMod, "c_load_model");
if( pFuncLoadmodel == NULL ){
printf("GetFunc pFuncLoadmodel error\n");
return -1;
}
//2.1 构建传入参数 读取模型的路径
pLoadModelParm = Py_BuildValue( "(s)", "/home" );
if(pLoadModelParm == NULL){
printf("BuildValue pLoadModelParm error\n");
return -1;
}
//2.2 调用c_load_model函数,并传递参数,返回值pModel为读取到的学习模型
pModel = PyObject_CallObject(pFuncLoadmodel, pLoadModelParm);
if(pModel == NULL){
printf("CallObject pModel error\n");
return -1;
}
//3.导入函数 c_load_charlist
pFuncLoadcharlist = PyObject_GetAttrString(pMod, "c_load_charlist");
if( pFuncLoadcharlist == NULL ){
printf("GetFunc pFuncLoadcharlist error\n");
return -1;
}
//3.1 构建传入参数 读取模型的路径
pLoadcharlistParm = Py_BuildValue( "(s)", " " );
if(pLoadcharlistParm == NULL){
printf("BuildValue pLoadcharlistParm error\n");
return -1;
}
//3.2 调用c_load_charlist函数,并传递参数
pCharList = PyObject_CallObject(pFuncLoadcharlist, pLoadcharlistParm);
if(pCharList == NULL){
printf("CallObject pCharList error\n");
return -1;
}
//4.导入函数 c_predict
pFuncPredict = PyObject_GetAttrString(pMod, "c_predict");
if( pFuncPredict == NULL ){
printf("GetFunc pFuncPredict error\n");
return -1;
}
//4.1 构建传入参数
pDNSName = Py_BuildValue( "s", pcDNSName);
pPredictParm = PyTuple_Pack(3, pDNSName, pModel, pCharList);//将构建的DNSName和之前保存的pModel、pCharList指针,构建为python元组传入
//pPredictParm = Py_BuildValue( "(sOO)", pcDNSName, pModel, pCharList);
if(pPredictParm == NULL){
printf("BuildValue pPredictParm error\n");
return -1;
}
//4.2 调用c_predict函数,并传递参数,返回值域名检测结果(float)
pDNSNameRst = PyObject_CallObject(pFuncPredict, pPredictParm);
if(pCharList == NULL){
printf("CallObject pDNSNameRst error\n");
return -1;
}
//解析返回值
float ret;
PyArg_Parse( pDNSNameRst, "f", &ret );
printf("C:%f\n",ret);
//回收python资源
Py_XDECREF(pMod);
Py_XDECREF(pFuncLoadmodel);
Py_XDECREF(pLoadModelParm);
Py_XDECREF(pModel);
Py_XDECREF(pFuncLoadcharlist);
Py_XDECREF(pLoadcharlistParm);
Py_XDECREF(pCharList);
Py_XDECREF(pFuncPredict);
Py_XDECREF(pDNSName);
Py_XDECREF(pPredictParm);
Py_XDECREF(pDNSNameRst);
Py_Finalize();
return 0;
}