LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 1910|回复: 3

为python编写c/c++ extension

[复制链接]
发表于 2005-10-26 22:33:47 | 显示全部楼层 |阅读模式
为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的这三种数据类型:

  1. // build an integer
  2. PyObject* pInt = Py_BuildValue("i", 2003);
  3. assert(PyInt_Check(pInt));

  4. int i = PyInt_AsLong(pInt);
  5. Py_DECREF(pInt);

  6. // build a float
  7. PyObject* pFloat = Py_BuildValue("f", 3.14f);
  8. assert(PyFloat_Check(pFloat));

  9. float f = PyFloat_AsDouble(pFloat);
  10. Py_DECREF(pFloat);

  11. // build a string
  12. PyObject* pString = Py_BuildValue("s", "Python");
  13. assert(PyString_Check(pString);

  14. int nLen = PyString_Size(pString);
  15. char* s = PyString_AsString(pString);
  16. Py_DECREF(pString);
复制代码

1.1.2 元组

Python语言中的元组是一个长度固定的数组,当Python解释器调用C语言扩展中的方法时,所有非关键字(non-keyword)参数都以元组方式进行传递。下面的例子示范了如何在C语言中使用Python的元组类型:

  1. // create the tuple
  2. PyObject* pTuple = PyTuple_New(3);
  3. assert(PyTuple_Check(pTuple));
  4. assert(PyTuple_Size(pTuple) == 3);

  5. // set the item
  6. PyTuple_SetItem(pTuple, 0, Py_BuildValue("i", 2003));
  7. PyTuple_SetItem(pTuple, 1, Py_BuildValue("f", 3.14f));
  8. PyTuple_SetItem(pTuple, 2, Py_BuildValue("s", "Python"));

  9. // parse tuple items
  10. int i;
  11. float f;
  12. char *s;
  13. if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))
  14.     PyErr_SetString(PyExc_TypeError, "invalid parameter");

  15. // cleanup
  16. Py_DECREF(pTuple);
复制代码

1.1.3列表

Python语言中的列表是一个长度可变的数组,列表比元组更为灵活,使用列表可以对其存储的Python对象进行随机访问。下面的例子示范了如何在C语言中使用Python的列表类型:

  1. // create the list
  2. PyObject* pList = PyList_New(3); // new reference
  3. assert(PyList_Check(pList));

  4. // set some initial values
  5. for(int i = 0; i < 3; ++i)
  6.     PyList_SetItem(pList, i, Py_BuildValue("i", i));

  7. // insert an item
  8. PyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));

  9. // append an item
  10. PyList_Append(pList, Py_BuildValue("s", "appended"));

  11. // sort the list
  12. PyList_Sort(pList);

  13. // reverse the list
  14. PyList_Reverse(pList);

  15. // fetch and manipulate a list slice
  16. PyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new reference
  17. for(int j = 0; j < PyList_Size(pSlice); ++j) {
  18.   PyObject *pValue = PyList_GetItem(pList, j);
  19.   assert(pValue);
  20. }
  21. Py_DECREF(pSlice);

  22. // cleanup
  23. Py_DECREF(pList);
复制代码

1.1.4 字典

Python语言中的字典是一个根据关键字进行访问的数据类型。下面的例子示范了如何在C语言中使用Python的字典类型:

  1. // create the dictionary
  2. PyObject* pDict = PyDict_New(); // new reference
  3. assert(PyDict_Check(pDict));

  4. // add a few named values
  5. PyDict_SetItemString(pDict, "first",
  6.                      Py_BuildValue("i", 2003));
  7. PyDict_SetItemString(pDict, "second",
  8.                      Py_BuildValue("f", 3.14f));

  9. // enumerate all named values
  10. PyObject* pKeys = PyDict_Keys(); // new reference
  11. for(int i = 0; i < PyList_Size(pKeys); ++i) {
  12.   PyObject *pKey = PyList_GetItem(pKeys, i);
  13.   PyObject *pValue = PyDict_GetItem(pDict, pKey);
  14.   assert(pValue);
  15. }
  16. Py_DECREF(pKeys);

  17. // remove a named value
  18. PyDict_DelItemString(pDict, "second");

  19. // cleanup
  20. Py_DECREF(pDict);

复制代码

2 接口文件

接口文件是一个c/c++文件,它由三部分构成
1.导出函数
2.方法列表
3.初始化函数

2.1 导出函数

要在Python解释器中使用c/c++语言中的某个函数,首先要为其编写相应的导出函数,在接口文件中,所有的导出函数都具有相同的函数原型:

  1. 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,如下所示:

  1. PyObject* method(PyObject *self, PyObject *argvs)
  2. {
  3.   Py_INCREF(Py_None);
  4.   return Py_None;
  5. }
复制代码

对于argvs通常会用PyArg_ParseTuple() 函数来;解析,其原型为:
int PyArg_ParseTuple(PyObject *arg, char *format, ...)

举些例子:

  1.     int ok;
  2.     int i, j;
  3.     long k, l;
  4.     char *s;
  5.     int size;

  6.     ok = PyArg_ParseTuple(args, ""); /* No arguments */
  7.         /* Python call: f() */

  8.     ok = PyArg_ParseTuple(args, "s", &s); /* A string */
  9.         /* Possible Python call: f('whoops!') */

  10.     ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
  11.         /* Possible Python call: f(1, 2, 'three') */

  12.     ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
  13.         /* A pair of ints and a string, whose size is also returned */
  14.         /* Possible Python call: f((1, 2), 'three') */

  15.     {
  16.         char *file;
  17.         char *mode = "r";
  18.         int bufsize = 0;
  19.         ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
  20.         /* A string, and optionally another string and an integer */
  21.         /* Possible Python calls:
  22.            f('spam')
  23.            f('spam', 'w')
  24.            f('spam', 'wb', 100000) */
  25.     }

  26.     {
  27.         int left, top, right, bottom, h, v;
  28.         ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
  29.                  &left, &top, &right, &bottom, &h, &v);
  30.         /* A rectangle and a point */
  31.         /* Possible Python call:
  32.            f(((0, 0), (400, 300)), (10, 10)) */
  33.     }

  34.     {
  35.         Py_complex c;
  36.         ok = PyArg_ParseTuple(args, "D:myfunction", &c);
  37.         /* a complex, also providing a function name for errors */
  38.         /* Possible Python call: myfunction(1+2j) */
  39.     }
复制代码

2.2 方法列表

方法列表中给出了所有可以被Python解释器使用的方法,假设我们已经定义了一个导出函数 SampleMethod
则对应的方法列表为:

  1. static PyMethodDef exampleMethods[] =
  2. {
  3.   {"method", SampleMethod, METH_VARARGS, "some comment"},
  4.   {NULL, NULL}
  5. };
复制代码

其中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来说,则相应的初始化函数为:

  1. DL_EXPORT(void) initexample()
  2. {
  3.   PyObject* m;
  4.   m = Py_InitModule("example", exampleMethods);
  5. }
复制代码


如果为c++,则需要加extern "C" 关键字

  1. extern "C"
  2. {
  3.   DL_EXPORT(void) initexample()
  4.   {
  5.     Py_InitModule("example", exampleMethods);
  6.   }
  7. }
复制代码

下面给一个完整的接口文件的例子,修改相应的部分就能使用

  1. //interface file for python, coded by jumbo 2005.7.25
  2. #include <python2.4/Python.h>
  3. #include "emmanager.h"
  4. // Add two arbitrary objects
  5. static PyObject *emulator_calc(PyObject *pSelf, PyObject *pArgs)
  6. {
  7.   int routenum, wavenum, buffernum, bufferstorage,prio_algo,;
  8.     char *servtype;
  9.     char *wcenable;
  10.     char *servprio;
  11.     float speed,load,rsvbuf;

  12.     if (!PyArg_ParseTuple(pArgs,"iiisfifssif", &routenum, &wavenum,&buffernum,&servtype,&speed,&bufferstorage,&load,&wcenable,&servprio,&prio_algo,&rsvbuf))
  13.       return NULL;
  14.     try
  15.       {
  16.         EmManager::GetManager()->Run(routenum, wavenum,buffernum,servtype,speed,bufferstorage,load,wcenable,servprio,prio_algo,rsvbuf);   
  17.       }
  18.     catch(Err &a)
  19.       {
  20.         cout<<"a.GetErr()"<<endl;
  21.       }

  22.     Py_INCREF(Py_None);
  23.     return Py_None;
  24. }

  25. static PyObject *emulator_getlostrateall(PyObject *pSelf, PyObject *pArgs)
  26. {
  27.   if (!PyArg_ParseTuple(pArgs,""))
  28.     {
  29.       return NULL;
  30.     }

  31.   float lostrate = EmManager::GetManager()->GetLostRateAll();
  32.   return Py_BuildValue("f",lostrate);
  33. }


  34. static PyObject *emulator_getlostratebesteffort(PyObject *pSelf, PyObject *pArgs)
  35. {
  36.   if (!PyArg_ParseTuple(pArgs,""))
  37.     {
  38.       return NULL;
  39.     }

  40.   float lostrate = EmManager::GetManager()->GetLostRateBestEffort();
  41.   return Py_BuildValue("f",lostrate);
  42. }

  43. static PyObject *emulator_getlostratelowloss(PyObject *pSelf, PyObject *pArgs)
  44. {
  45.   if (!PyArg_ParseTuple(pArgs,""))
  46.     {
  47.       return NULL;
  48.     }

  49.   float lostrate = EmManager::GetManager()->GetLostRateLowLoss();
  50.   return Py_BuildValue("f",lostrate);
  51. }

  52. // Map of function names to functions
  53. static PyMethodDef emulator_method[] =
  54. {
  55.   {"Calc",emulator_calc, METH_VARARGS, "Calculate the loss probility of optical XC"},
  56.   {"GetLostRateAll",emulator_getlostrateall, METH_VARARGS, "Get the lost rate for all packages"},
  57.   {"GetLostRateLowLoss",emulator_getlostratelowloss, METH_VARARGS, "Get the lost rate for low loss packages"},
  58.   {"GetLostRateBestEffort",emulator_getlostratebesteffort, METH_VARARGS, "Get the lost rate for best effort packages"},
  59.   {NULL, NULL} // End of functions
  60. };
  61. // For C++,  should  declared extern "C"
  62. extern "C"
  63. {
  64.   DL_EXPORT(void) initemulator()
  65.   {
  66.     Py_InitModule("emulator", emulator_method);
  67.   }
  68. }
复制代码


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结尾。

  1. from distutils.core import setup, Extension

  2. module1 = Extension('emulator',
  3.                     define_macros = [('MAJOR_VERSION', '1'),
  4.                                      ('MINOR_VERSION', '0')],
  5.                     include_dirs = ['/usr/include'],
  6.                     libraries = ['gsl','gslcblas'],
  7.                     library_dirs = ['/usr/lib'],
  8.                     sources = ['moduleem.cpp', 'process.cpp', 'emulator.cpp','random.cpp','prio.cpp','emmanager.cpp'])

  9. setup (name = 'emulator',
  10.        version = '1.0',
  11.        description = 'This is a alpha package',
  12.        author = 'jumbo',
  13.        author_email = 'jumbon@hotmail.com',
  14.        url = 'http://www.python.org/doc/current/ext/building.html',
  15.        long_description = '''
  16. This is really just a demo package.
  17. ''',
  18.        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
发表于 2005-10-27 15:35:45 | 显示全部楼层
也可以用 swig 来写 C 扩展

用 swig 的好处是同样的 C 代码可以用来扩展 python , php , perl
回复 支持 反对

使用道具 举报

发表于 2005-10-28 12:05:23 | 显示全部楼层
有没有漂亮一点的实现?看着一堆Py_foo就头痛。
有没有完全面向对象,或者使用模版的方式来产生extension?
回复 支持 反对

使用道具 举报

发表于 2005-10-28 12:06:28 | 显示全部楼层
有一个中间的产品,pyrex,感觉怪怪的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表