能够使用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.html
https://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 <Python.h>
然后再定义一个模块的初始化函数。
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 -*-
- 第一个是一个字符串,表示在Python中对应的方法的名称;
from setuptools import setup, Extension
hello_world = Extension(‘lc_hello_world’, sources=[“lc_hello.c”])
setup(ext_modules=[hello_world])
然后再执行命令进行编译:
$ python setup.py build
执行成功后会在当前目录下的build目录中生成扩展模块文件。
测试
最后就是编写一个小程序来测试刚刚的模块是否可用。
import lc_hello_world
print(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')) # 这个会报错