使用C/C++ 扩展Python

前期的网页抽取算法使用C++开发,为了提升代码复用,减少维护成本,项目中决定封装成Python扩展方便Python使用。
Python与C/C++互操作有很多方案:Python C API, swig, sip, ctypes, cpython, cffi, boost.python等。这里选择了最原始的Python C API方式。

一、开发前准备

1.Python对象

大多数Python对象在Python解析器中都为PyObject,在C代码中只能声明PyObject*类型的python对象,然后使用该对象对应的初始化函数初始化。如PyTuple_New,PyList_New,PyDict_New,Py_BuildValue等。
例如构建一个{‘a’:{‘b’:['123','34']}}对象

?
1
2
3
4
5
6
7
PyObject* obj = PyDict_New();
PyObject* b = PyDict_New();
PyObject* c = PyList_New(2);
PyList_SetItem(c, 0, Py_BuildValue( "s" "123" ));
PyList_SetItem(c, 1, Py_BuildValue( "s" "34" ));
PyDict_SetItem(a,  "b" , c);
PyDict_SetItem(obj,  "a" , a);

Python对象问题这里有一些文档:

http://docs.python.org/2/c-api/intro.html#objects-types-and-reference-counts

http://docs.python.org/2/c-api/dict.html

http://docs.python.org/2/c-api/list.html

2.Python内存管理

Python对象管理采用引用技术模型,内部有一些复杂的循环引用等处理措施。主要有 Py_INCREF() / Py_DECREF()两个宏负责处理。具体文档可以看这里http://docs.python.org/2/c-api/intro.html#reference-counts

例如上一点申请的对象obj如果需要释放怎么办?不可以直接free/delete,直接Py_DECREF(obj),然后obj = NULL即可,否则会报错。

3.线程安全

Python由于历史比较悠久,作者在开发的时候可能并没有考虑到多线程这个东西,因为Python的内存管理并不是线程安全的。在后来后来版本中为了处理这个线程安全问题引入了GIL即global interpreter lock。这是一个粗粒度的锁,执行Python ByteCode之前都会取得这个锁。以至于Python的多线程比较鸡肋,GIL也就成了性能瓶颈。这个问题很多地方都有讨论,我之前有一篇文章专门对这个问题进行了说明,感兴趣的同学请去这里http://in.sdo.com/?p=1623。

有人会问为什么不设计更细粒度的锁?实际上有人已经进行了尝试,但是为了不增加实现的复杂性也就一直没有加到CPython中。其他版本的python如IronPython等对这个问题已经做了改善。

实际开发时有两种情况需要关心:
1).释放锁
这种情景只要在进行IO或CPU繁重的计算时,暂时释放GIL使得其他线程的代码可以执行。
2).取得锁
主要出现在C回调Python代码

参考文档:

http://docs.python.org/2/c-api/init.html#thread-state-and-the-global-interpreter-lock

二、开发扩展

有了上面的知识我们开始进行实际的开发。

1.导出函数

写好C API函数之后我们需要导出,写一个函数描述表即可,如下面的EchoMethods,一定要以NULL结尾。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PyObject* echo(PyObject* self, PyObject* args)
{
         char * input = NULL;
         if (!PyArg_ParseTuple(args,  "s" , &input))
         {
                 printf ( "parse arg error\n" );
                 return NULL;
         }
 
         int count = 0;
         do
         {
                 printf ( "%s\n" , input);
                 count++;
         } while (count < 100);
         return Py_BuildValue( "i" , 0);
}
 
static PyMethodDef EchoMethods[] =
{
         { "echo" , (PyCFunction)echo, METH_VARARGS},
         {NULL, NULL}
};

2.导出对象

除了上面提到的使用复杂的PyObject操作语法封装一个Python对象返回之外还有其他途径,如直接导出C的Struct到Python。这里不详谈,需要的可以查相关资料。

3.初始化模块

模块初始化调用Py_InitModule,传入模块名和模块的方法描述表即可。如果初始化失败会返回error可以做相应处理。

?
1
2
3
4
PyMODINIT_FUNC initecho()
{
         Py_InitModule( "echo" , EchoMethods);
}

三、编译与使用

1.如何编译、分发、使用

上面这些代码当然会用到python-devel库。编译的时候使用GCC直接编译成一般的so,就可以直接在python里面调用了。Python会自己选择如何加载这个so。

?
1
2
g++ -c  echo .c -I  /usr/include/python2 .7 /include/python2 .7 -fPIC
g++ -shared  echo .o -o  echo .so

上面已经提到了,实际上把自己编译好的so放在PYTHONPATH路径中的任意一个下面都可以直接调用了。

2.更便捷的方式

上面的编译方式可以自己写一个Makefile处理起来更灵活,实际上Python有一个更方便的处理方式。使用distutils包,编译安装一步到位,这也是easy_install等工具使用的方式。
上面这个简单使用distutils处理起来像这样:

?
1
2
3
4
5
6
7
8
from distutils.core  import setup, Extension
echomodule  = Extension( 'echo' ,
                         sources  = [ 'echo.c' ])
setup(name  = 'echo' ,
         version  = '1.0' ,
         description  = 'test' ,
         author  = "dudu"
         ext_modules  = [echomodule])

Extension对象定义一个扩展的源文件、需要用到的第三方库、头文件、特殊的编译选项等等,而setup则定义安装的规则及扩展的一些属性。

使用的时候执行下面两个命令就可以了。

?
1
2
python setup.py build
sudo python setup.py  install

这部分可以参考http://docs.python.org/2/distutils/apiref.html

文章是写完了。特别推荐需要开发许多接口的人去看看开头提到的swig/sip等等,这些项目只需要编写简单的规则,就可以为c/c++中的方法生成wrapper。我只所以有采用c api是因为需求简单,需要暴露给python的总共也没几个函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值