背景:
Python语言在数据处理流程中使用非常方便,但是众所周知,python虽然简单方便,但是却避免不了解释性语言的弊端——运行速度慢。而c语言较为底层,在速度方面能够超越大部分编程语言。使用c语言扩展python接口能够充分利用python的简单易用的优点,同时拥有c语言运行速度快的优点。
本文将通过介绍我们工程中经常使用的python字典数据类型的数据的处理,了解c语言扩展python接口的流程。使大家能够对相关的转换函数进行转换尽快上手,提高程序的运行速度。
流程:
网上的教程有很多,但是使用的流程大致分为以下几步:
1、模块初始化:定义模块名字,并指定模块定义的函数。
2、函数列表:指定模块包含哪些接口,接口名字,接口实际调用的函数。
3、函数实现:函数的具体实现。
4、编译运行
模块初始化:
函数名和文件名保持一致,本文使用的文件名为AI.c则函数名为initAI
代码示例:
void initAI() {
Py_InitModule("AI", AIMethods);
}
代码说明:
初始化模块,模块名为AI,其定义的接口在AIMethods函数中定义。
函数列表:
定义模块接口,指定模块包含哪些接口,接口名字,接口实际调用的函数,参数传递方式等。最后一个必须增加一行NULL。
代码示例:
static PyMethodDef
AIMethods[] = {
{"feature_get_exponential_sum_with_proportion",feature_get_exponential_sum_with_proportion,METH_VARARGS},
,
};
代码说明:
定义名为“feature_get_exponential_sum_with_proportion”的接口(名字可以和实际调用的函数名不一样),实际调用的函数为feature_get_exponential_sum_with_proportion,,METH_VARARGS是函数传递参数的方式。
函数实现:
本示例中使用的函数在python中的代码实现如下:
def feature_get_exponential_sum_with_proportion(data, segment_pair,newsegment):
"""
interest with exponential function decay
get the exponential_sum from segment_pair[0] withthe segment_pair[1]
:param data: dict
:param segment_pair: first item is origin data andis list, second item is list and is condition
:param new_segment: list, new key corresponding tothe key of transformed data
:return: data self
"""
data1, data2 = data[segment_pair[0]],data[segment_pair[1]]
s = 0
for i, value in enumerate(data1):
s += 1.0 / (np.log10(i +1) + 1) * value / data2[i] / len(data1)
data[newsegment] = [s]
return data
可以看出代码非常简洁明了,短短五六行就完成了整个函数的具体实现,但是当我们使用c语言来对这个函数进行实现呢?代码如下:
static PyObject * feature_get_exponential_sum_with_proportion(PyObject*self, PyObject *args)
{
PyObject *dict;
PyObject * py_key_pair;
char * new_key;
PyObject * py_value1;
PyObject * py_value2;
if(!PyArg_ParseTuple(args, "OOs",&dict,&py_key_pair,&new_key))
returnNULL;
PyObject * k1 =PyTuple_GetItem(py_key_pair,0);
PyObject * k2 = PyTuple_GetItem(py_key_pair,1);
char * key1 =PyString_AS_STRING(k1);
char * key2 =PyString_AS_STRING(k2);
// printf("%s%s\n",key1,key2);
py_value1 =PyDict_GetItemString(dict,key1); //
py_value2 =PyDict_GetItemString(dict,key2); //
int v1_len =PyList_Size(py_value1);
int i;
PyObject* pList =PyList_New(0); // new reference:empty list
//assert(PyList_Check(pList));
doubleresult=0;
for(i=0;i
doublev1,v2;
PyObject*v1_temp;//
v1_temp= PyList_GetItem(py_value1,i);
v1=PyFloat_AsDouble(v1_temp);
// printf("%d%f\n",i,v1);
PyObject* v2_temp;
v2_temp= PyList_GetItem(py_value2,i);
v2= PyFloat_AsDouble(v2_temp);
result+=(1.0/(log10(i+1)+1)*v1/v2/v1_len);
}
PyList_Append(pList,Py_BuildValue("d", result));
PyDict_SetItemString(dict,new_key,Py_BuildValue("O",pList));
Py_DECREF(pList);
Py_INCREF(dict);
return dict;
}
我们可以看到,代码相对来说复杂了很多,但是仔细看可以看出,多出的代码更多的在于类型的转换,即将python类型的数据转换成c语言类型的数据。
代码说明:
if (!PyArg_ParseTuple(args, "OOs",&dict,&py_key_pair,&new_key))
returnNULL;
对函数的参数进行解析,“OOs”指明参数的数据类型,“O”代表PyObject数据类型,“s”指明字符串数据类型。当然,还有其他的数据类型,请查看本文给出的参考文档。
PyObject * k1 = PyTuple_GetItem(py_key_pair,0);
PyObject * k2 = PyTuple_GetItem(py_key_pair,1);
py_key_pair是tuple数据类型,上面两行代码得到tuple数据类型的第一项和第二项数据。
char * key1 = PyString_AS_STRING(k1);
char * key2 = PyString_AS_STRING(k2);
将它们从PyString数据类型转换c语言能够使用的string字符串数据类型。
py_value1 = PyDict_GetItemString(dict,key1); // value_K_i
py_value2 = PyDict_GetItemString(dict,key2); // value_K_i
根据key1,和key2得到dict字典数据类型的数据。
//上面几行可以用更加简单的操作来实现。如下:
//Py_value1 = PyDict_GetItem(dict,k1);
//Py_value2 = PyDict_GetItem(dict,k2);
int v1_len = PyList_Size(py_value1);
得到list的长度。
PyObject* pList = PyList_New(0);
生成一个空的list。
PyList_Append(pList, Py_BuildValue("d", result));
往pList中添加数据,数据类型为double,数值为result
PyDict_SetItemString(dict,new_key,Py_BuildValue("O",pList));
增加一个键值对,键为new_key,值为pList
Py_DECREF(pList);
Py_INCREF(dict);
对dict增加一个引用计数
return dict;
返回字典。
编译链接:
gcc -fpic -c –I /usr/include/python2.2 -I /usr/lib/python2.2/config AI.c这个操作的-I命令可以指定编译所依赖的头文件所在的位置
gcc -shared -o AI.so AI.o
这部分也可以通过标准python构建系统distutils从setup.py编译C扩展,这相当方便。这里不对其进行介绍。
运行结果:(图)
运行比较:第一个时间是使用python代码运行的时间,第二个是c语言扩展python接口运行的时间,可以看到c语言实现的代码有了明显的加速。
总结:
C语言扩展python接口从实际运行中可以发现确实增速不少,所以当我们的模型性能遇到瓶颈时,不妨通过C语言修改特征的转换函数。但是实际工程量方面却增加很多,所有实际运作时还是要兼顾两个方面的影响。
Tips:遇到的几个坑
1,Error:takes no keyword argument报错:如果使用METH_VARARGS参数传递方式,那么c语言传递参数的时候不能指定参数名,所以接口不能使用类似func(a=1)这样的参数,使用func(1),如果需要使用键值对参数,则需要使用METH_KEYWORDS。
2,TypeError: bad argument type for built-in operation:可能是GetItem时访问数据越界造成的。
3,引用计数增加很重要,没有正确增减对象引用计数将会导致致命错误。
4,float类型在数据精度要求非常高的情况下会出现精度不准确的情况,转换成double数据类型可以有效的解决。
参考文档:
1.官方文档:https://docs.python.org/2.7/c-api/
2.官方示例:https://en.wikibooks.org/wiki/Python_Programming/Extending_with_C
3.用C语言扩展Python的功能https://www.ibm.com/developerworks/cn/linux/l-pythc/
4.Extending C program using Python unableto parse dictionary to C function using swighttps://stackoverflow.com/questions/26246195/extending-c-program-using-python-unable-to-parse-dictionary-to-c-function-using
5,使用numpy数据类型https://segmentfault.com/a/1190000000479951
领取专属 10元无门槛券
私享最新 技术干货