C程序中直接调用Python函数(Linux)

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;
}
  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值