为python编写c/c++ 的extension

为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的这三种数据类型:
[CODE]
// 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);
[/CODE]
 1.1.2 元组
Python语言中的元组是一个长度固定的数组,当Python解释器调用C语言扩展中的方法时,所有非关键字(non-keyword)参数都以元组方式进行传递。下面的例子示范了如何在C语言中使用Python的元组类型:
[CODE]
// 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);
[/CODE]
1.1.3列表
Python语言中的列表是一个长度可变的数组,列表比元组更为灵活,使用列表可以对其存储的Python对象进行随机访问。下面的例子示范了如何在C语言中使用Python的列表类型:
[CODE]
// 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);
[/CODE]
 1.1.4 字典
Python语言中的字典是一个根据关键字进行访问的数据类型。下面的例子示范了如何在C语言中使用Python的字典类型:
[CODE]
// 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);
[/CODE]
2 接口文件
接口文件是一个c/c++文件,它由三部分构成
1.导出函数
2.方法列表
3.初始化函数
2.1 导出函数
要在Python解释器中使用c/c++语言中的某个函数,首先要为其编写相应的导出函数,在接口文件中,所有的导出函数都具有相同的函数原型:
[CODE]
PyObject* method(PyObject* self, PyObject* argvs);
[/CODE]
该函数是Python解释器和C函数进行交互的接口,带有两个参数:self和args。参数self只在C函数被实现为内联方法(built- in method)时才被用到,通常该参数的值为空(NULL)。参数args中包含了Python解释器要传递给C函数的所有参数,通常使用Python的 C语言扩展接口提供的函数PyArg_ParseTuple()来获得这些参数值。
所有的导出函数都返回一个PyObject指针,如果对应的C函数没有真正的返回值(即返回值类型为void),则应返回一个全局的None对象(Py_None),并将其引用计数增1,如下所示:
[CODE]
PyObject* method(PyObject *self, PyObject *argvs)
{
  Py_INCREF(Py_None);
  return Py_None;
}
[/CODE]
对于argvs通常会用PyArg_ParseTuple() 函数来;解析,其原型为:
int PyArg_ParseTuple(PyObject *arg, char *format, ...)
举些例子:
[CODE]
    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) */
    }
[/CODE]
2.2 方法列表
方法列表中给出了所有可以被Python解释器使用的方法,假设我们已经定义了一个导出函数 SampleMethod
则对应的方法列表为:
[CODE]
static PyMethodDef exampleMethods[] =
{
  {"method", SampleMethod, METH_VARARGS, "some comment"},
  {NULL, NULL}
};
[/CODE]
其中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来说,则相应的初始化函数为:
[CODE]
DL_EXPORT(void) initexample()
{
  PyObject* m;
  m = Py_InitModule("example", exampleMethods);
}
[/CODE]
如果为c++,则需要加extern "C" 关键字
[CODE]
extern "C"
{
  DL_EXPORT(void) initexample()
  {
    Py_InitModule("example", exampleMethods);
  }
}
[/CODE]
下面给一个完整的接口文件的例子,修改相应的部分就能使用
[CODE]
//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);
  }
}
[/CODE]
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结尾。
[CODE]
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])
[/CODE]
上述是一个demo的setup文件,只需要修改相应的地方就能使用,其中source源不需要写h文件。具体的
请参考disutils的说明文档。
然后编译就能使用了
python setup.py build
将生成的*.so文件直接拷贝到python文件可以找的地方,比如当前python文件目录或者/usr/lib , /usr/local/lib
4 引用
直接import刚才生成的文件就可以使用接口文件的函数了:
from example import *

参考:
本文参考了肖文鹏的《用C语言扩展Python的功能》,地址在:
 [url]http://www-128.ibm.com/developerworks/cn/linux/l-pythc/index.html[/url]
以及python网站关于extending and embedding the Python Interpreter的文章,地址在:
[url]http://www.python.org/doc/2.0.1/ext/ext.html[/url]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值