Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始受到越来越多人的青睐,并且被屡屡成功地应用于各类大型软件系统的开发过程中。
与其它普通脚本语言有所不同,Python程序员可以借助Python语言提供的API,使用C或者C++来对Python进行功能性扩展,从而即可以利用Python方便灵活的语法和功能,又可以获得与C或者C++几乎相同的执行性能。执行速度慢是几乎所有脚本语言都具有的共性,也是倍受人们指责的一个重要因素,Python则通过与C语言的有机结合巧妙地解决了这一问题,从而使脚本语言的应用范围得到了很大扩展。
在用Python开发实际软件系统时,很多时候都需要使用C/C++来对Python进行扩展。最常见的情况是目前已经存在一个用C编写的库,需要在Python语言中使用该库的某些功能,此时就可以借助Python提供的扩展功能来实现。此外,由于Python从本质上讲还是一种脚本语言,某些功能用Python实现可能很难满足实际软件系统对执行效率的要求,此时也可以借助Python提供的扩展功能,将这些关键代码段用C或者C++实现,从而提供程序的执行性能。
本文主要介绍Python提供的C语言扩展接口,以及如何使用这些接口和C/C++语言来对Python进行功能性扩展,并辅以具体的实例讲述如何实现Python的功能扩展。
1. Python的C语言接口
Python是用C语言实现的一种脚本语言,本身具有优良的开放性和可扩展性,并提供了方便灵活的应用程序接口(API),从而使得C/C++程序员能够在各个级别上对Python解释器的功能进行扩展。在使用C/C++对Python进行功能扩展之前,必须首先掌握Python解释所提供的C语言接口。
1.1 Python对象(PyObject)
Python是一门面向对象的脚本语言,所有的对象在Python解释器中都被表示成PyObject,PyObject结构包含Python对象的所有成员指针,并且对Python对象的类型信息和引用计数进行维护。在进行Python的扩展编程时,一旦要在C或者C++中对Python对象进行处理,就意味着要维护一个PyObject结构。
在Python的C语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针。
1.2 引用计数
为了简化内存管理,Python通过引用计数机制实现了自动的垃圾回收功能,Python中的每个对象都有一个引用计数,用来计数该对象在不同场所分别被引用了多少次。每当引用一次Python对象,相应的引用计数就增1,每当消毁一次Python对象,则相应的引用就减1,只有当引用计数为零时,才真正从内存中删除Python对象。
下面的例子说明了Python解释器如何利用引用计数来对Pyhon对象进行管理:
例1:refcount.py
classrefcount:
# etc.
r1=refcount()# 引用计数为1
r2=r1# 引用计数为2
del(r1)# 引用计数为1
del(r2)# 引用计数为0,删除对象
在C/C++中处理Python对象时,对引用计数进行正确的维护是一个关键问题,处理不好将很容易产生内存泄漏。Python的C语言接口提供了一些宏来对引用计数进行维护,最常见的是用Py_INCREF()来增加使Python对象的引用计数增1,用Py_DECREF()来使Python对象的引用计数减1。
1.3 数据类型
Python定义了六种数据类型:整型、浮点型、字符串、元组、列表和字典,在使用C语言对Python进行功能扩展时,首先要了解如何在C和Python的数据类型间进行转化。
整型、浮点型和字符串
在Python的C语言扩展中要用到整型、浮点型和字符串这三种数据类型时相对比较简单,只需要知道如何生成和维护它们就可以了。下面的例子给出了如何在C语言中使用Python的这三种数据类型:
17例2:typeifs.c
// build an integer
PyObject*pInt=Py_BuildValue("i",2003);
assert(PyInt_Check(pInt));
inti=PyInt_AsLong(pInt);
Py_DECREF(pInt);
// build a float
PyObject*pFloat=Py_BuildValue("f",3.14f);
assert(PyFloat_Check(pFloat));
floatf=PyFloat_AsDouble(pFloat);
Py_DECREF(pFloat);
// build a string
PyObject*pString=Py_BuildValue("s","Python");
assert(PyString_Check(pString);
intnLen=PyString_Size(pString);
char*s=PyString_AsString(pString);
Py_DECREF(pString);
元组
Python语言中的元组是一个长度固定的数组,当Python解释器调用C语言扩展中的方法时,所有非关键字(non-keyword)参数都以元组方式进行传递。下面的例子示范了如何在C语言中使用Python的元组类型:
17例3:typetuple.c
// create the tuple
PyObject*pTuple=PyTuple_New(3);
assert(PyTuple_Check(pTuple));
assert(PyTuple_Size(pTuple)==3);
// set the item
PyTuple_SetItem(pTuple,0,Py_BuildValue("i",2003));
PyTuple_SetItem(pTuple,1,Py_BuildValue("f",3.14f));
PyTuple_SetItem(pTuple,2,Py_BuildValue("s","Python"));
// parse tuple items
inti;
floatf;
char*s;
if(!PyArg_ParseTuple(pTuple,"ifs",&i,&f,&s))
PyErr_SetString(PyExc_TypeError,"invalid parameter");
// cleanup
Py_DECREF(pTuple);
列表
Python语言中的列表是一个长度可变的数组,列表比元组更为灵活,使用列表可以对其存储的Python对象进行随机访问。下面的例子示范了如何在C语言中使用Python的列表类型:
Python
24例4:typelist.c
//createthelist
PyObject*pList=PyList_New(3);//newreference
assert(PyList_Check(pList));
//setsomeinitialvalues
for(inti=0;i<3;++i)
PyList_SetItem(pList,i,Py_BuildValue("i",i));
//insertanitem
PyList_Insert(pList,2,Py_BuildValue("s","inserted"));
//appendanitem
PyList_Append(pList,Py_BuildValue("s","appended"));
//sortthelist
PyList_Sort(pList);
//reversethelist
PyList_Reverse(pList);
//fetchandmanipulatealistslice
PyObject*pSlice=PyList_GetSlice(pList,2,4);//newreference
for(intj=0;j<PyList_Size(pSlice);++j){
PyObject*pValue=PyList_GetItem(pList,j);
assert(pValue);
}
Py_DECREF(pSlice);
//cleanup
Py_DECREF(pList);
字典
Python语言中的字典是一个根据关键字进行访问的数据类型。下面的例子示范了如何在C语言中使用Python的字典类型:
Python
21例5:typedic.c
//createthedictionary
PyObject*pDict=PyDict_New();//newreference
assert(PyDict_Check(pDict));
//addafewnamedvalues
PyDict_SetItemString(pDict,"first",
Py_BuildValue("i",2003));
PyDict_SetItemString(pDict,"second",
Py_BuildValue("f",3.14f));
//enumerateallnamedvalues
PyObject*pKeys=PyDict_Keys();//newreference
for(inti=0;i<PyList_Size(pKeys);++i){
PyObject*pKey=PyList_GetItem(pKeys,i);
PyObject*pValue=PyDict_GetItem(pDict,pKey);
assert(pValue);
}
Py_DECREF(pKeys);
//removeanamedvalue
PyDict_DelItemString(pDict,"second");
//cleanup
Py_DECREF(pDict);
2. Python的C语言扩展
2.1 模块封装
在了解了Python的C语言接口后,就可以利用Python解释器提供的这些接口来编写Python的C语言扩展,假设有如下一个C语言函数:
Python
8例6:example.c
intfact(intn)
{
if(n<=1)
return1;
else
returnn*fact(n-1);
}
该函数的功能是计算某个给定自然数的阶乘,如果想在Python解释器中调用该函数,则应该首先将其实现为Python中的一个模块,这需要编写相应的封装接口,如下所示:
21例7:wrap.c
#include
PyObject*wrap_fact(PyObject*self,PyObject*args)
{
intn,result;
if(!PyArg_ParseTuple(args,"i:fact",&n))
returnNULL;
result=fact(n);
returnPy_BuildValue("i",result);
}
staticPyMethodDefexampleMethods[]=
{
{"fact",wrap_fact,METH_VARARGS,"Caculate N!"},
{NULL,NULL}
};
voidinitexample()
{
PyObject*m;
m=Py_InitModule("example",exampleMethods);
}
一个典型的Python扩展模块至少应该包含三个部分:导出函数、方法列表和初始化函数。
2.2 导出函数
要在Python解释器中使用C语言中的某个函数,首先要为其编写相应的导出函数,上述例子中的导出函数为wrap_fact。在Python的C语言扩展中,所有的导出函数都具有相同的函数原型:
PyObject*method(PyObject*self,PyObject*args);
该函数是Python解释器和C函数进行交互的接口,带有两个参数:self和args。参数self只在C函数被实现为内联方法(built-in method)时才被用到,通常该参数的值为空(NULL)。参数args中包含了Python解释器要传递给C函数的所有参数,通常使用Python的C语言扩展接口提供的函数PyArg_ParseTuple()来获得这些参数值。
所有的导出函数都返回一个PyObject指针,如果对应的C函数没有真正的返回值(即返回值类型为void),则应返回一个全局的None对象(Py_None),并将其引用计数增1,如下所示:
PyObject*method(PyObject *self,PyObject *args)
{
Py_INCREF(Py_None);
returnPy_None;
}
2.3 方法列表
方法列表中给出了所有可以被Python解释器使用的方法,上述例子对应的方法列表为:
staticPyMethodDefexampleMethods[]=
{
{"fact",wrap_fact,METH_VARARGS,"Caculate N!"},
{NULL,NULL}
};
方法列表中的每项由四个部分组成:方法名、导出函数、参数传递方式和方法描述。方法名是从Python解释器中调用该方法时所使用的名字。参数传递方式则规定了Python向C函数传递参数的具体形式,可选的两种方式是METH_VARARGS和METH_KEYWORDS,其中METH_VARARGS是参数传递的标准形式,它通过Python的元组在Python解释器和C函数之间传递参数,若采用METH_KEYWORD方式,则Python解释器和C函数之间将通过Python的字典类型在两者之间进行参数传递。
2.4 初始化函数
所有的Python扩展模块都必须要有一个初始化函数,以便Python解释器能够对模块进行正确的初始化。Python解释器规定所有的初始化函数的函数名都必须以init开头,并加上模块的名字。对于模块example来说,则相应的初始化函数为:
voidinitexample()
{
PyObject*m;
m=Py_InitModule("example",exampleMethods);
}
当Python解释器需要导入该模块时,将根据该模块的名称查找相应的初始化函数,一旦找到则调用该函数进行相应的初始化工作,初始化函数则通过调用Python的C语言扩展接口所提供的函数Py_InitModule(),来向Python解释器注册该模块中所有可以用到的方法。
2.5 编译链接
要在Python解释器中使用C语言编写的扩展模块,必须将其编译成动态链接库的形式。下面以RedHat Linux 8.0为例,
-I/usr/lib/python2.2/config
example.cwrapper.c
1.8 引入Python解释器
当生成Python扩展模块的动态链接库后,就可以在Python解释器中使用该扩展模块了,与Python自带的模块一样,扩展模块也是通过import命令引入后再使用的,如下所示:
Python2.2.1(#1, Aug 30 2002, 12:15:30)
[GCC3.220020822(RedHatLinuxRawhide3.2-4)]onlinux2
Type"help","copyright","credits"or"license"formoreinformation.
>>>importexample
>>>example.fact(4)
24
>>>
3. 结束语
作为一门功能强大的脚本语言,Python将被更加广泛地应用于各个领域。为了克服脚本语言执行速度慢的问题,Python提供了相应的C语言扩展接口,通过将影响执行性能的关键代码用C语言实现,可以很大程度上提高用Python编写的脚本在运行时的速度,从而满足实际需要。