背景
用Python开发程序速度快,并且有许多现成模块可供使用,但执行速度相对较慢;C语言则正好相反,其执行速度快,但开发效率低。
为了充分利用两种语言各自的优点,比较好的做法是用Python开发整个软件框架,而用C语言实现其关键模块。Python目前的主流版本是3,本文探究如何使用C语言扩展Python3。
系统环境
Ubuntu 14.04 LTS x64 + Python 3.4.0 + gcc 4.8.2
C该怎么写?
0、新建一个C文件
话不多说,先新建一个C文件,命名 demo.c。
1、包含必要的头文件
所有的PythonAPI必须包含 Python.h,这个文件包含了<stdio.h>、<string.h>、<errno.h>和<stdlib.h>。
2、开始写功能函数
1) 函数
现在就可以开始写你想用C执行的函数了。这个函数的格式是有要求的,一个简单的函数如下所示:
1 | static PyObject * |
我们来慢慢看这个函数。
首先函数类型必须是 static PyObject *,函数名可以随便取,函数接收的参数必须是 (PyObject *self, PyObject *args)。
所有从python传过来的参数都在 args 里,但是C和Python的数据类型不同,为了使C可以顺利的使用参数,我们需要在这之间做转化。
这里使用了 PyArg_ParseTuple 函数(点击进入官方文档),其中的第二个参数是匹配字符串,函数根据这个匹配字符串将args的值传给后面的若干的参数。该例中“U”代表字符串,name的类型实际上可以定义为 char *。
这个例子实际上没有使用任何C语言的数据类型,实际上PyArg_ParseTuple 函数提取出来的就是C的数据类型。
1 | char *name; |
上面这个例子,python函数的参数是一个字符串,一个整型。通过PyArg_ParseTuple 函数转换成了C语言的 char* 和 int。
按C语言的数据类型处理完数据,就要返回了,显然直接返回也是不行的。我们使用 Py_BuildValue 函数将C语言的数据转换成Python的数据,这实际上就是上文PyArg_ParseTuple的逆过程。
1 | char total[10000]; |
上面的代码在Python中调用之后收到的返回值就是 “strcat() 函数用来连接字符串:tset”。
数据转换还有几个其他的函数,匹配字符串也有很多种写法。更多详细的请看官方文档。
2) 模块结构
写好了方法之后,我们需要构建一个方法表,如下所示
1 | // method table |
每个方法需要关注的变量是浅两个,第一个变量是你在python中调用的名称,第二个变量是你的C语言函数名称,这两个名称会通过这个表匹配。第三个设置你函数传入参数的情况,第四个只是个描述,填 NULL 也行。
DemoMethods中可以包含多个方法,每个方法按照这个依样画葫芦就成。这个数组实际上就是个 Method Table,记录了你所有的方法匹配表。
实际上我们提供给Python的是一个模块,上面只是构建了方法表,下面再将 DemoMethods 作为变量传入,构建模块结构,如下所示。
1 | // The method table must be referenced in the module definition structure. |
3) 初始化
上面定义的结构,必须在模块的初始化函数中传给解释器。初始化函数必须命名为 PyInit_name(),其中name是模块的名称,这应该是在模块文件中定义的唯一的非静态项目:
1 | // The initialization function must be named PyInit_name() |
至此,C语言模块代码已经编写完成。
C该怎么编译?
代码写好就应该开始准备编译为动态链接库方便python调用了。这里可以参考文档《Building C and C++ Extensions with distutils》,方法很简单:新建文件setup.py,配置编译:
1 | from distutils.core import setup, Extension |
打开终端:
python3 setup.py build
在 build 目录下可以找到编译好的 so 文件。
Python怎么调用?
想怎么用就怎么用。so文件和py文件放一起,下面是最简单的例子。
1 | import demo |
有趣的研究
Python传入的参数是tuple该怎么匹配
如果Python调用函数传入的参数是tuple,PyArg_ParseTuple函数的匹配字符串可以通过( )来加以区分。
1 | import demo |
C语言中PyArg_ParseTuple匹配字符串可以通过()来匹配tuple的元素。
1 | static PyObject * |
上面代码输出的结果是 “2SinkMiku”。这些功能的核心就是PyArg_ParseTuple函数中的匹配字符串,写好这个字符串可以处理很多复杂的数据结构。
源代码已上传至OSC