众所周知,作为解释型语言的 Python 可不是什么超级快速的语言,但许多复杂的库函数(比如 NumPy 库)却能执行得相当快速。这主要是因为这些库的核心代码往往是用 C 或者 C++ 写好,并经过了编译,比解释执行的 Python 代码有更快的执行速度。
在这篇短文中,我们将详细聊一聊如何用 C 或者 C++ 写一个 Python 模组(或软件包),内容主要参考 Python 官方文档。作为范例,我也将用 C 写一个简单的 Python 模组,完成一个简单的数学计算: n!=n×(n-1)×(n-2)… 。为了实现上面的目标,我们需要两个文件:一个 Python 代码 setup.py,以及我们实际编写的 C 语言代码 cmath.c。
总的来说,我们将用 setup.py 把 C 语言写的代码 cmath.c 构建成一个 Python 库(这其中包括编译代码、查找 Python C 库、连接等操作)。
原理
为了让我们的程序/模组能在 Python 代码中被调用执行,模组需要和 Python 解释器 CPython 进行必要的通讯。因此,我们需要 Python.h 头文件里面的若干对象,并用它们构建出合适的结构体。
基本上,我们要做的是把实际的 C 语言方法包装起来,以便能够被 Python 解释器所调用,这样我们的 Python 代码才能够像使用普通的 Python 函数一样,调用这个方法。
编写算法并包装
首先,我们要在 cmath.c 里引入头文件:
#include Python.h
在 Python 头文件里,我们需要用来和 Python 解释器对接的对象(以及函数),都以 Py 开头。在这里,能代表所有 python 对象的 C 对象(基本上就是一个opaque——“不透明”对象)叫做 PyObject。
不过,在实际使用这些对象之前,我们先把求阶乘的算法写出来(注意,0的阶乘是1):
int fastfactorial(int n){
if(n<=1)
return 1;
else
return n * fastfactorial(n-1);
}
接着,我们给这个函数进行一下包装。这个包裹函数接收一个 PyObject 类型的指针(指向今后从 Python 代码传入的参数)作为参数,再返回一个 PyObject 类型的指针(指向上面函数的返回值)给外部。
为此,我们用以下代码来实现这个包裹函数:
static PyObject* factorial(PyObject* self, PyObject* args){
int n;
if (!PyArg_ParseTuple(args,"i",&n))
return NULL;
int result = fastfactorial(n);
return Py_BuildValue("i",result);
}
这个函数始终需要一个指向模组对象本身的 self 指针,以及一个指向从 Python 代码传入参数的 args 指针(二者都是 PyObject 类型的对象)。我们用 PyArg_ParseTuple 方法来处理这些参数,并且声明我们需要的是整数类型(第二个参数 "i"),最后将处理结果赋值到变量 n 中。
接着自然是调用 fastfactorial(n) 来计算阶乘,并用 Python 头文件里的 Py_BuildValue 方法把返回值塞回 PyObject* 类型里。最后,我们的包裹函数将指向结果的指针对象返回给外部。
组装模组结构
现在,我们已经把实际的阶乘函数封装完毕,接下来需要构造一个 PyModuleDef 结构体的实例(这个对象也是由 Python.h 所定义的。这个结构体定义了模组的结构,以便 Python 解释器载入调用。而模组的另一个组成部分是定义它的所有方法,这由另一个结构体 PyMethodDef 实现——它其实就相当于一个数组,里面列出了模组中所有的方法和对应的说明。
在当前例子中,我们定义了如下的 PyMethodDef 对象:
static PyMethodDef mainMethods[] = {
{"factorial",factorial,METH_VARARGS,"Calculate the factorial of n"},
{NULL,NULL,0,NULL}
};
这个对象里目前共有 2 个元素——我们在最末尾加入了一个由 NULL 组成的结构体,做为结尾。第 0 个对象是我们定义的方法,它的结构是:先是方法名 factorial,其次是实际调用的函数对象,注意这里调用的是上一节定义的包裹函数;接下来指定了这个方法是从 METH_VARARGS 这个常量中获得它的参数;最后是一个说明字符串。
于是,我们已经定义了这个 Python 模组中的所有方法(本例中就一个),我们可以创建一个 PyModuleDef 的实例,作为代表整个 Python 模组的对象。
代码如下:
static PyModuleDef cmath = {
PyModuleDef_HEAD_INIT,
"cmath","Factorial Calculation",
-1,
mainMethods
};
在上面的代码中,我们首先定义了模组名 cmath 以及简短的文档字符串,然后再把所有的方法组成的数组 mainMethods 放进去。
最后一步,我们要添加一个函数,并让 python 代码导入这个模组的时候执行这个函数。
代码如下:
PyMODINIT_FUNC PyInit_cmath(void){
return PyModule_Create(&cmath);
}
函数的返回类型是 PyMODINIT_FUNC,这表明函数实际上返回的是一个 PyObject 类型的指针。这个指针指向由 PyModule_Create 生成的 Python 模组本身(这个模组对象本身也是一个 PyObject对象)。当一个模组被 Python 代码导入时,这个方法就会被调用,并返回一个指向整个模组对象,包含了所有方法的指针。
原文链接:https://www.jianshu.com/p/6e37be94c3f5
网友评论