|
为python写c或c++ 扩展
python是现在比较流行的编程语言,使用非常容易,功能也很强大,但是执行效率较c或c++差很多,在写某些项目的时候可以先用python把软件框架快速搭建起来,然后用c或者c++改写某些瓶颈模块,使得软件的效率跟c或c++的编制的代码相差无几,从而达到性能和快速开发的平衡。或者将某些c和c++编制的老代码重新封装一下,使python直接调用,从而达到软件复用的目的。
但是怎么让python直接调用c或者c++编写的代码呢,其实很简单,只需写一个封装文件,作为python和c/c++程序直接的接口。
1 数据的转换
python其实就是c语言编写的,它是一种面向对象的语言,把任何数据结构都解释为对象,变量是对象,类是对象,函数是对象,统统都是对象,所以编写接口程序的时候要理解的是c/c++程序的变量转换为python的变量的时候需要把c/c++的变量转换为python的对象,在python里边,这些对象的类型就是PyObject:
1.1数据类型
Python定义了六种数据类型:整型、浮点型、字符串、元组、列表和字典,在使用C语言对Python进行功能扩展时,首先要了解如何在C和Python的数据类型间进行转化。
1.1.1 整型、浮点型和字符串
在Python的C语言扩展中要用到整型、浮点型和字符串这三种数据类型时相对比较简单,只需要知道如何生成和维护它们就可以了。下面的例子给出了如何在C语言中使用Python的这三种数据类型:
- // build an integer
- PyObject* pInt = Py_BuildValue("i", 2003);
- assert(PyInt_Check(pInt));
- int i = PyInt_AsLong(pInt);
- Py_DECREF(pInt);
- // build a float
- PyObject* pFloat = Py_BuildValue("f", 3.14f);
- assert(PyFloat_Check(pFloat));
- float f = PyFloat_AsDouble(pFloat);
- Py_DECREF(pFloat);
- // build a string
- PyObject* pString = Py_BuildValue("s", "Python");
- assert(PyString_Check(pString);
- int nLen = PyString_Size(pString);
- char* s = PyString_AsString(pString);
- Py_DECREF(pString);
复制代码
1.1.2 元组
Python语言中的元组是一个长度固定的数组,当Python解释器调用C语言扩展中的方法时,所有非关键字(non-keyword)参数都以元组方式进行传递。下面的例子示范了如何在C语言中使用Python的元组类型:
- // 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
- int i;
- float f;
- char *s;
- if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))
- PyErr_SetString(PyExc_TypeError, "invalid parameter");
- // cleanup
- Py_DECREF(pTuple);
复制代码
1.1.3列表
Python语言中的列表是一个长度可变的数组,列表比元组更为灵活,使用列表可以对其存储的Python对象进行随机访问。下面的例子示范了如何在C语言中使用Python的列表类型:
- // create the list
- PyObject* pList = PyList_New(3); // new reference
- assert(PyList_Check(pList));
- // set some initial values
- for(int i = 0; i < 3; ++i)
- PyList_SetItem(pList, i, Py_BuildValue("i", i));
- // insert an item
- PyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));
- // append an item
- PyList_Append(pList, Py_BuildValue("s", "appended"));
- // sort the list
- PyList_Sort(pList);
- // reverse the list
- PyList_Reverse(pList);
- // fetch and manipulate a list slice
- PyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new reference
- for(int j = 0; j < PyList_Size(pSlice); ++j) {
- PyObject *pValue = PyList_GetItem(pList, j);
- assert(pValue);
- }
- Py_DECREF(pSlice);
- // cleanup
- Py_DECREF(pList);
复制代码
1.1.4 字典
Python语言中的字典是一个根据关键字进行访问的数据类型。下面的例子示范了如何在C语言中使用Python的字典类型:
- // create the dictionary
- PyObject* pDict = PyDict_New(); // new reference
- assert(PyDict_Check(pDict));
- // add a few named values
- PyDict_SetItemString(pDict, "first",
- Py_BuildValue("i", 2003));
- PyDict_SetItemString(pDict, "second",
- Py_BuildValue("f", 3.14f));
- // enumerate all named values
- PyObject* pKeys = PyDict_Keys(); // new reference
- for(int i = 0; i < PyList_Size(pKeys); ++i) {
- PyObject *pKey = PyList_GetItem(pKeys, i);
- PyObject *pValue = PyDict_GetItem(pDict, pKey);
- assert(pValue);
- }
- Py_DECREF(pKeys);
- // remove a named value
- PyDict_DelItemString(pDict, "second");
- // cleanup
- Py_DECREF(pDict);
复制代码
2 接口文件
接口文件是一个c/c++文件,它由三部分构成
1.导出函数
2.方法列表
3.初始化函数
2.1 导出函数
要在Python解释器中使用c/c++语言中的某个函数,首先要为其编写相应的导出函数,在接口文件中,所有的导出函数都具有相同的函数原型:
- PyObject* method(PyObject* self, PyObject* argvs);
复制代码
该函数是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 *argvs)
- {
- Py_INCREF(Py_None);
- return Py_None;
- }
复制代码
对于argvs通常会用PyArg_ParseTuple() 函数来;解析,其原型为:
int PyArg_ParseTuple(PyObject *arg, char *format, ...)
举些例子:
- int ok;
- int i, j;
- long k, l;
- char *s;
- int size;
- ok = PyArg_ParseTuple(args, ""); /* No arguments */
- /* Python call: f() */
- ok = PyArg_ParseTuple(args, "s", &s); /* A string */
- /* Possible Python call: f('whoops!') */
- ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
- /* Possible Python call: f(1, 2, 'three') */
- ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
- /* A pair of ints and a string, whose size is also returned */
- /* Possible Python call: f((1, 2), 'three') */
- {
- char *file;
- char *mode = "r";
- int bufsize = 0;
- ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
- /* A string, and optionally another string and an integer */
- /* Possible Python calls:
- f('spam')
- f('spam', 'w')
- f('spam', 'wb', 100000) */
- }
- {
- int left, top, right, bottom, h, v;
- ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
- &left, &top, &right, &bottom, &h, &v);
- /* A rectangle and a point */
- /* Possible Python call:
- f(((0, 0), (400, 300)), (10, 10)) */
- }
- {
- Py_complex c;
- ok = PyArg_ParseTuple(args, "D:myfunction", &c);
- /* a complex, also providing a function name for errors */
- /* Possible Python call: myfunction(1+2j) */
- }
复制代码
2.2 方法列表
方法列表中给出了所有可以被Python解释器使用的方法,假设我们已经定义了一个导出函数 SampleMethod
则对应的方法列表为:
- static PyMethodDef exampleMethods[] =
- {
- {"method", SampleMethod, METH_VARARGS, "some comment"},
- {NULL, NULL}
- };
复制代码
其中method为python调用c/c++函数时用的名称,而不是我们在接口文件中定义的SampleMethod(),方法列表将定义的函数和调用名称对应起来。METH_VARARGS是参数传递方式,可选的两种方式是METH_VARARGS和METH_KEYWORDS,其中METH_VARARGS是参数传递的标准形式,它通过Python的元组在Python解释器和C函数之间传递参数,若采用METH_KEYWORD方式,则Python解释器和C函数之间将通过Python的字典类型在两者之间进行参数传递。
2.3 初始化函数
所有的Python扩展模块都必须要有一个初始化函数,以便Python解释器能够对模块进行正确的初始化。Python解释器规定所有的初始化函数的函数名都必须以init开头,并加上模块的名字。对于模块example来说,则相应的初始化函数为:
- DL_EXPORT(void) initexample()
- {
- PyObject* m;
- m = Py_InitModule("example", exampleMethods);
- }
复制代码
如果为c++,则需要加extern "C" 关键字
- extern "C"
- {
- DL_EXPORT(void) initexample()
- {
- Py_InitModule("example", exampleMethods);
- }
- }
复制代码
下面给一个完整的接口文件的例子,修改相应的部分就能使用
- //interface file for python, coded by jumbo 2005.7.25
- #include <python2.4/Python.h>
- #include "emmanager.h"
- // Add two arbitrary objects
- static PyObject *emulator_calc(PyObject *pSelf, PyObject *pArgs)
- {
- int routenum, wavenum, buffernum, bufferstorage,prio_algo,;
- char *servtype;
- char *wcenable;
- char *servprio;
- float speed,load,rsvbuf;
- if (!PyArg_ParseTuple(pArgs,"iiisfifssif", &routenum, &wavenum,&buffernum,&servtype,&speed,&bufferstorage,&load,&wcenable,&servprio,&prio_algo,&rsvbuf))
- return NULL;
- try
- {
- EmManager::GetManager()->Run(routenum, wavenum,buffernum,servtype,speed,bufferstorage,load,wcenable,servprio,prio_algo,rsvbuf);
- }
- catch(Err &a)
- {
- cout<<"a.GetErr()"<<endl;
- }
- Py_INCREF(Py_None);
- return Py_None;
- }
- static PyObject *emulator_getlostrateall(PyObject *pSelf, PyObject *pArgs)
- {
- if (!PyArg_ParseTuple(pArgs,""))
- {
- return NULL;
- }
- float lostrate = EmManager::GetManager()->GetLostRateAll();
- return Py_BuildValue("f",lostrate);
- }
- static PyObject *emulator_getlostratebesteffort(PyObject *pSelf, PyObject *pArgs)
- {
- if (!PyArg_ParseTuple(pArgs,""))
- {
- return NULL;
- }
- float lostrate = EmManager::GetManager()->GetLostRateBestEffort();
- return Py_BuildValue("f",lostrate);
- }
- static PyObject *emulator_getlostratelowloss(PyObject *pSelf, PyObject *pArgs)
- {
- if (!PyArg_ParseTuple(pArgs,""))
- {
- return NULL;
- }
- float lostrate = EmManager::GetManager()->GetLostRateLowLoss();
- return Py_BuildValue("f",lostrate);
- }
- // Map of function names to functions
- static PyMethodDef emulator_method[] =
- {
- {"Calc",emulator_calc, METH_VARARGS, "Calculate the loss probility of optical XC"},
- {"GetLostRateAll",emulator_getlostrateall, METH_VARARGS, "Get the lost rate for all packages"},
- {"GetLostRateLowLoss",emulator_getlostratelowloss, METH_VARARGS, "Get the lost rate for low loss packages"},
- {"GetLostRateBestEffort",emulator_getlostratebesteffort, METH_VARARGS, "Get the lost rate for best effort packages"},
- {NULL, NULL} // End of functions
- };
- // For C++, should declared extern "C"
- extern "C"
- {
- DL_EXPORT(void) initemulator()
- {
- Py_InitModule("emulator", emulator_method);
- }
- }
复制代码
3 编译发布
本文只讨论Linux下的编译和发布方法,windows下需要使用VC6编译dll文件,暂不讨论。
1 直接编译
在文件数目较少的情况下直接编译很方便,使用gcc或者g++,加share参数:
gcc -fpic -c interface.c -o example.o
gcc -fpic -c realcode.c -o realcode.o
gcc -shared -o example.so example.o realcode.o
2 编写setup.py
使用python自带的utilitis工具很容易将我们刚才编写的接口文件和c/c++源代码编译为python可
使用的动态共享文件,通常是以*.so结尾。
- from distutils.core import setup, Extension
- module1 = Extension('emulator',
- define_macros = [('MAJOR_VERSION', '1'),
- ('MINOR_VERSION', '0')],
- include_dirs = ['/usr/include'],
- libraries = ['gsl','gslcblas'],
- library_dirs = ['/usr/lib'],
- sources = ['moduleem.cpp', 'process.cpp', 'emulator.cpp','random.cpp','prio.cpp','emmanager.cpp'])
- setup (name = 'emulator',
- version = '1.0',
- description = 'This is a alpha package',
- author = 'jumbo',
- author_email = 'jumbon@hotmail.com',
- url = 'http://www.python.org/doc/current/ext/building.html',
- long_description = '''
- This is really just a demo package.
- ''',
- ext_modules = [module1])
复制代码
上述是一个demo的setup文件,只需要修改相应的地方就能使用,其中source源不需要写h文件。具体的
请参考disutils的说明文档。
然后编译就能使用了
python setup.py build
将生成的*.so文件直接拷贝到python文件可以找的地方,比如当前python文件目录或者/usr/lib , /usr/local/lib
4 引用
直接import刚才生成的文件就可以使用接口文件的函数了:
from example import *
参考:
本文参考了肖文鹏的《用C语言扩展Python的功能》,地址在:
http://www-128.ibm.com/developerworks/cn/linux/l-pythc/index.html
以及python网站关于extending and embedding the Python Interpreter的文章,地址在:
http://www.python.org/doc/2.0.1/ext/ext.html |
|