能够使用C语言编写扩展是Python一大卖点吧,这可以将一些关键的代码使用C来写以提升程序的性能。本文是参考了Python的官方文档整理而来的,同时结合了Python2跟Python3。按照惯例现在先从一个Hello World开始讲解一下写扩展的基本流程。
详细的内容可以参考官方文档:https://docs.python.org/2.7/extending/index.html https://docs.python.org/3/extending/index.html
https://docs.python.org/2.7/c-api/index.htmlhttps://docs.python.org/3/c-api/index.html
同时本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。
首先介绍一下我当前的开发环境: * ArchLinux * gcc 4.8.2 * glibc 2.19 * Python 2.7.6 * Python 3.3.5
开始
先创建一个新的C代码文件 lc_hello.c。为了能够正常使用python的api,需要导入Python.h这个头文件。
#include
然后再定义一个模块的初始化函数。
PyMODINIT_FUNC initlc_hello_world(void){ Py_InitModule("lc_hello_world", lc_hello_world_methods); printf("init lc_hello_world module\n");}
这个函数是用于模块初始化的,即是在第一次使用import语句导入模块时会执行。其函数名必须为initmodule_name这样的格式,在这里我们的模块名为lc_hello_world,所以函数名就是initlc_hello_world。在这个函数中又调用了Py_InitModule函数,它执行了模块初始化的操作。Py_InitModule函数传入了两个参数,第一个参数为字符串,表示模块的名称;第二个参数是一个PyMethodDef的结构体数组,表示该模块都具有哪些方法。与Py_InitModule相似的方法还有Py_InitModule3和Py_InitModule4。因此在initlc_hello_world方法之前还需要先定义 lc_hello_world_methods 数组。
static PyMethodDef lc_hello_world_methods[] = { {"test", (PyCFunction)test_function, METH_NOARGS, "lc_hello_world extending test"}, {"add", (PyCFunction)add_function, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL}};
PyMethodDef结构体有四个字段。 * 第一个是一个字符串,表示在Python中对应的方法的名称; * 第二个是对应的C代码的函数; * 第三个是一个标致位,表示该Python方法是否需要参数,METH_NOARGS表示不需要参数,METH_VARARGS表示需要参数; * 第四个是一个字符串,它是该方法的__doc__属性,这个不是必须的,可以为NULL。PyMethodDef结构体数组最后以 {NULL, NULL, 0, NULL}结尾。(感觉好像不是必须的,但是通常都这么做那我们也这么做吧)
注意:以上的用法都是针对Python2的,在Python3中又有些不同。在Python3中模块的初始化函数的函数名变为了PyInit_module_name这样的形式了,因此这里就需要定义一个函数 PyMODINIT_FUNC PyInit_lc_hello_world。并且还需要返回一个 module 类型的变量。其次在Python3中创建module对象的函数也由 Py_InitModule 变为了 PyModule_Create。因此在Python3中模块的初始化函数应该定义如下:
PyMODINIT_FUNC PyInit_lc_hello_world(void){ PyObject *m; m = PyModule_Create(&lc_hello_world_module); if (m == NULL) return NULL; printf("init lc_hello_world module\n"); return m;}
PyModule_Create函数需要传入一个 PyModuleDef 类型的指针。因此在此之前还需要先定义 lc_hello_world_module 变量。
static struct PyModuleDef lc_hello_world_module = { PyModuleDef_HEAD_INIT, "lc_hello_world", /* name of module */ NULL, /* module documentation, may be NULL */ -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ lc_hello_world_methods /* A pointer to a table of module-level functions, described by PyMethodDef values. Can be NULL if no functions are present. */};
在 lc_hello_world_methods 中我们为模块指定了两个方法,接下来我们需要实现这两个方法。
static PyObject* test_function(PyObject *self){ PyObject_Print(self, stdout, 0); printf("lc_hello_world test\n"); Py_INCREF(Py_True); return Py_True;}
这段代码定义了Python的test方法所对应的C函数。在这个函数中就只执行了一条printf语句,然后就返回了Py_True。Py_True即是Python中的True,Py_INCREF函数执行的操作是对Python对象的计数引用值进行加1。与Py_INCREF对应的是Py_DECREF,它是对计数引用减1,并且计数引用为0时就销毁对象并回收内存。
static PyObject* add_function(PyObject *self, PyObject *args){ int num1, num2; PyObject *result=NULL; if (!PyArg_ParseTuple(args, "nn", &num1, &num2)) { printf("传入参数错误!\n"); return NULL; } result = PyInt_FromLong(num1+num2); return result;}
这须代码定义了Python的add方法所对应的C函数。该函数需要传入两个整数类型的参数。PyArg_ParseTuple是对传入的参数进行解析,关于这个函数的说明请查看Python手册。
注意:在Python3中整数都是 long 类型的,因此这里的 PyInt_FromLong 需要改为 PyLong_FromLong,其作用是将C的int类型转为Python的int类型。
编译
扩展模块编写完成后,接下来就是对其进行编译了。先编写一个 setup.py 脚本。
#!/usr/bin/env python#-*- coding:utf-8 -*-from setuptools import setup, Extensionhello_world = Extension('lc_hello_world', sources=["lc_hello.c"])setup(ext_modules=[hello_world])
然后再执行命令进行编译:
$ python setup.py build
执行成功后会在当前目录下的build目录中生成扩展模块文件。
测试
最后就是编写一个小程序来测试刚刚的模块是否可用。
import lc_hello_worldprint(lc_hello_world.test.__doc__)print(lc_hello_world.add.__doc__)print(lc_hello_world.test())print(lc_hello_world.add(1, 2))print(lc_hello_world.add(1, '2')) # 这个会报错