用C/C++为Python实现扩展模块简介

Python在机器学习、大数据处理方面得到了广泛的应用。虽然Python是一门解释执行的语言,但是许多计算密集型操作(如NumPy等)仍然有很高的性能,远远超过其它解释语言,如PHP等。这是因为许多计算密集型代码都用C/C++实现,然后以库的形式提供给Python调用。本文用一个简单的例子展示如何使用C/C++实现Python模块。

斐波那契数列

假设我们需要实现高性能的斐波那契数列,计划用C/C++实现。这里只为展示用C/C++实现Python模块,所以实现方法并不一定是效率最后的办法。斐波那契数列定义如下:

F(0) = 0, F(1) = 1,
F(n) = F(n - 1) + F (n - 2) (n >= 2)

需要实现一个函数,传入参数n,返回对应的斐波那契数。

C/C++实现

定义文件fibo.c,并实现函数fibonacci计算斐波那契数。

unsigned int fibonacci(unsigned int n) {
   if (n <= 1)
       return n;
   return fibonacci(n - 1) + fibonacci(n - 2);
}

注意这里的实现并不是很严谨,因为整型为32位整数,最大只能表示到第47个斐波那契数,由于这并不是本文重点,忽略不计。 以上是计算斐波那契数的C/C++实现,为了Python中能调用这个方法,我们需要根据Pyhton规范添加一些额外的东东。实际上最重要的是以下两项:

  • 封装C/C++函数使其能被Python调用
  • 装饰C模块使其像一个Python模块

首先在fibo.c文件中引入头文件python.h,该头文件定义了必须的数据结构及一些方便开发的的函数和宏。

#include "Python.h"

以Python解释器能理解的方式封装函数:

static PyObject* fibonacci(PyObject* self, PyObject* args) {
    int n;
    if (!PyArg_ParseTuple(args, "i", &n))
        return NULL;
    int num = fibonacci(n);
    return Py_BuildValue("i", num);
}

其中,指针self指向的PyObject代表Python调用时的模块对象,而指针args指向的PyObject代表Python调用时的参数列表。函数PyArg_ParseTuple用来解析参数。当计算完成时,用函数Py_BuildValue生成PyObject返回给调用的Python模块。这里可以看到传入参数及返回的结果都是Python可理解的数据结构。

Python中每引入一个模块,都会生成一个模块的实例,实例的属性包含该模块中定义的变量及方法。我们的C文件中也需要有生成模块实例的能力,这是通过PyModuleDef机构实现的。

static PyMethodDef methods[] = {
    {"fibonacci", fibonacci, METH_VARARGS, "Get Fibonacci number"},
    {NULL, NULL, 0, NULL}
};

static PyModuleDef fibo = {
    PyModuleDef_HEAD_INIT,
    "fibo_test", "Calculate Fibonacci number via c/c++",
    -1,
    methods
};

这里声明了模块名及输出的方法。还需要一个方法在import时创建模块实例:

PyMODINIT_FUNC PyInit_fibo_test(void){
    return PyModule_Create(&fibo);
}

注意,创建实例的函数名为:PyInit_[Module Name]

编译和安装

现在所有Python需要的封装和定义都已经完成,下一步就是编译和安装。Python的distutils.core模块中的Extensionsetup方法使编译和安装非常容易。定义setup.py

from distutils.core import setup, Extension
fiboM = Extension('fibo_test',sources = ['fibo.c'])
setup(name = 'MathExtension',version='1.0',description = 'This is a test',ext_modules = [fiboM])

编译生成

用如下的命令来编译该模块,生成的.so文件会在build目录中:

python setup.py build

安装

用如下的命令来安装该模块,.so文件会被放在Python的库目录中,Python脚本可以直接访问:

python setup.py install

测试

由于该模块只是一个简单的示例,没有必要安装到Python库中,我们只使用build选项来完成测试。在笔者的测试环境中,生成之后.so文件位于./build/lib.linux-x86_64-3.5/fibo_test.cpython-35m-x86_64-linux-gnu.so,为了测试方便,在build目录下建一个软连接:

cd build
ln -s lib.linux-x86_64-3.5/fibo_test.cpython-35m-x86_64-linux-gnu.so fibo_test.so

下面在build目录下测试:

>>> from fibo_test import fibonacci as fb
>>> fb(0)
0
>>> fb(1)
1
>>> fb(2)
1
>>> fb(3)
2
>>> fb(4)
3
>>> fb(5)
5
>>> fb(10)
55
>>> fb(22)
17711

小结

以上简单快速的介绍了如何用C/C++实现Python模块,Python模块扩展接口的许多细节都没有深入介绍,有兴趣的读者可以阅读Python相关文档

本文所有代码在Python 3.5.2下测试通过,代码在git库c_extension目录下。