个性化阅读
专注于IT技术分析

python C语言扩展编程 – Python高级开发教程

点击下载

上一章Python教程请查看:python GUI编程

使用任何编译语言(如C、c++或Java)编写的任何代码都可以集成或导入到另一个Python脚本中,这段代码被认为是一个“扩展”。

Python扩展模块只不过是一个普通的C库,在Unix机器上,这些库通常以.so结尾(对于共享对象,UNIX动态链接库),在Windows机器上,通常会看到.dll(用于动态链接库)。

1、编写扩展的先决条件

要开始编写扩展,你需要Python头文件。

  • 在Unix机器上,这通常需要安装特定于开发人员的包,如python2.5-dev。
  • Windows用户在使用二进制Python安装程序时,将这些头文件作为包的一部分。

此外还假设你对C或c++有很好的了解,能够使用C编程编写任何Python扩展。

2、Python扩展

在第一次查看Python扩展模块时,需要将代码分为四部分

  • 头文件Python.h。
  • 要作为模块接口公开的C函数。
  • Python开发人员看到的函数名映射到扩展模块中的C函数的表。
  • 一个初始化函数。

4、头文件Python.h

你需要在C源文件中包含Python.h头文件,这使你能够访问用于将模块挂接到解释器中的内部Python API。

确保在你可能需要的任何其他头文件之前包含Python.h,你需要在include后面加上希望从Python调用的函数。

5、C函数

函数C实现的特征通常有以下三种形式

static PyObject *MyFunction( PyObject *self, PyObject *args );

static PyObject *MyFunctionWithKeywords(PyObject *self,
                                 PyObject *args,
                                 PyObject *kw);

static PyObject *MyFunctionWithNoArgs( PyObject *self );

前面的每个声明都返回一个Python对象,在Python中没有像在C中那样的void函数。如果你不想让你的函数返回一个值,就返回Python的None值的C等量词,Python头文件定义了一个宏Py_RETURN_NONE,它为我们完成了这个任务。

你的C函数的名称可以是任何你喜欢的名称,因为它们从未出现在扩展模块之外,它们被定义为静态函数。

C函数的命名通常是将Python模块和函数名组合在一起,如下所示

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* Doing... */
    Py_RETURN_NONE;
 }

这是模块模块内部一个名为func的Python函数。你将把指向C函数的指针放到方法表中,用于通常出现在源代码中的模块。

6、方法映射表

这个方法表是一个简单的PyMethodDef结构数组,这个结构是这样的:

struct PyMethodDef {
    char *ml_name;
    PyCFunction ml_meth;
    int ml_flags;
    char *ml_doc;
 };

下面是关于这种结构的各环节的说明

  • ml_name——这是Python解释器在Python程序中使用的函数名。
  • ml_meth——它必须是一个函数的地址,该函数具有前面所述的任何一个签名。
  • ml_flags——它告诉解释器ml_meth使用的是三个签名中的哪一个。
  • 该标志的值通常为METH_VARARGS。
  • 如果希望允许关键字参数进入函数,可以使用METH_KEYWORDS按位或’ed这个标志。
  • 它的值也可以是METH_NOARGS,表示不希望接受任何参数。
  • ml_doc——这是函数的文档字符串,如果你不想写它,它可以是空的。

此表需要使用一个标记来终止,该标记包含适当成员的NULL和0值。

对于上述定义的函数,我们有以下方法映射表

static PyMethodDef module_methods[] = {
    { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
    { NULL, NULL, 0, NULL }
 };

7、初始化函数

扩展模块的最后一部分是初始化功能,加载模块时,Python解释器将调用此函数,要求将函数命名为initModule,其中Module是模块的名称。

需要从将要构建的库中导出初始化函数, Python标头定义了PyMODINIT_FUNC,以使我们在其中编译的特定环境中发生这种情况,你需要做的就是在定义函数时使用它。

你的C初始化函数通常具有以下总体结构-

PyMODINIT_FUNC initModule() {
    Py_InitModule3(func, module_methods, "docstring...");
 }

下面是对Py_InitModule3函数的描述:

  • func这是要导出的函数。
  • module_methods——这是上面定义的映射表名。
  • docstring——这是你想在你的扩展中给出的注释。

综上所述,我们可以得出如下代码:

#include <Python.h>

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Doing... */
   Py_RETURN_NONE;
}

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

一个利用上述所有概念的简单示例

#include <Python.h>

static PyObject* helloworld(PyObject* self) {
   return Py_BuildValue("s", "Hello, Python扩展!!");
}

static char helloworld_docs[] =
   "helloworld( ): 任意信息!!\n";

static PyMethodDef helloworld_funcs[] = {
   {"helloworld", (PyCFunction)helloworld, 
      METH_NOARGS, helloworld_docs},
      {NULL}
};

void inithelloworld(void) {
   Py_InitModule3("helloworld", helloworld_funcs,
                  "扩展模块例子!");
}

这里使用Py_BuildValue函数来构建Python值,将上述代码保存到hello.c文件中,我们将看到如何编译和安装从Python脚本调用的这个模块。

8、构建和安装扩展

distutils包使得以标准方式分发Python模块(包括纯Python模块和扩展模块)变得非常容易。模块以源代码形式分发,并通过通常称为setup.py的安装脚本进行构建和安装,如下所示。

对于上述模块,你需要准备以下setup.py脚本

from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',  \
      ext_modules=[Extension('helloworld', ['hello.c'])])

现在,使用下面的命令,使用正确的编译器和链接器命令和标志执行所有需要的编译和链接步骤,并将生成的动态库复制到适当的目录中

$ python setup.py install

在基于unix的系统上,为了获得向site-packages目录写入的权限,你很可能需要以root身份运行这个命令,这通常不是Windows的问题。

9、导入扩展

一旦安装了扩展,就可以在Python脚本中导入并调用扩展,如下所示

#!/usr/bin/python
import helloworld

print helloworld.helloworld()

10、传递函数参数

由于你很可能希望定义接受参数的函数,所以可以为C函数使用其他签名之一。例如下面的函数,它接受一些参数,可以这样定义

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* 业务逻辑. */
    Py_RETURN_NONE;
 }

包含新函数项的方法表看起来像这样

static PyMethodDef module_methods[] = {
    { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
    { "func", module_func, METH_VARARGS, NULL },
    { NULL, NULL, 0, NULL }
 };

可以使用API PyArg_ParseTuple函数从传递到C函数的一个PyObject指针中提取参数。

PyArg_ParseTuple的第一个参数是args参数,这是你将要解析的对象,第二个参数是一个格式字符串,它描述了预期出现的参数。每个参数由格式字符串中的一个或多个字符表示,如下所示。

static PyObject *module_func(PyObject *self, PyObject *args) {
    int i;
    double d;
    char *s;
 
    if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
       return NULL;
    }
    
    /* Do something*/
    Py_RETURN_NONE;
 }

编译模块的新版本并导入它使你能够使用任意数量的任意类型的参数调用新函数

module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)

你可能会想出更多的变化。

11、Pyrg_ParseTuple函数

下面是PyArg_ParseTuple函数的标准签名

int PyArg_ParseTuple(PyObject* tuple,char* format,...)

这个函数为错误返回0,为成功返回一个不等于0的值。tuple是PyObject*,它是C函数的第二个参数。这里的格式是一个C字符串,它描述了强制参数和可选参数。

下面是PyArg_ParseTuple函数的格式代码列表

Code C type 解释
c char 长度为1的Python字符串变成了C字符。
d double Python浮点数变成了C double。
f float Python浮点数变成了C浮点数。
i int Python int变成了C int。
l long Python int变成了C长。
L long long Python int变成了C long long
O PyObject* 获取对Python参数的非空借来的引用。
s char* Python字符串,没有C字符*的内嵌空值。
s# char*+int 任何Python字符串到C地址和长度。
t# char*+int 只读单段缓冲区到C地址和长度。
u Py_UNICODE* Python Unicode没有嵌入空到C。
u# Py_UNICODE*+int 任何Python Unicode C地址和长度。
w# char*+int 读/写单段缓冲区到C地址和长度。
z char* 与s类似,也接受None(将C char*设置为NULL)。
z# char*+int 与s#类似,也接受None(将C char*设置为NULL)。
(…) as per … Python序列被视为每项一个参数。
|   以下参数是可选的。
:   格式化结束,然后是错误消息的函数名。
;   格式化结束,然后是整个错误消息文本。

12、返回值

Py_BuildValue采用与PyArg_ParseTuple类似的格式字符串,不是传递正在构建的值的地址,而是传递实际的值,下面是一个演示如何实现添加函数的例子:

static PyObject *foo_add(PyObject *self, PyObject *args) {
    int a;
    int b;
 
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
       return NULL;
    }
    return Py_BuildValue("i", a + b);
 }

这就是用Python实现时的样子

def add(a, b):
   return (a + b)

你可以从你的函数中返回两个值,如下所示,这将在Python中使用列表进行警告。

static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
    int a;
    int b;
 
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
       return NULL;
    }
    return Py_BuildValue("ii", a + b, a - b);
 }

这就是用Python实现时的样子

def add_subtract(a, b):
   return (a + b, a - b)

13、Py_BuildValue函数

下面是Py_BuildValue函数的标准签名

PyObject* Py_BuildValue(char* format,...)

这里的格式是描述要构建的Python对象的C字符串,Py_BuildValue的以下参数是生成结果的C值,PyObject*结果是一个新的引用。

下表列出了常用的代码字符串,其中0个或多个被连接成字符串格式。

Code C type 解释
c char C字符变成长度为1的Python字符串。
d double 一个C double变成了一个Python浮点数。
f float 一个C浮点数变成了一个Python浮点数。
i int C int变成了Python int。
l long 一个C long变成了一个Python int。
N PyObject* 传递Python对象并窃取引用。
O PyObject* 传递一个Python对象并按正常方式递增。
O& convert+void* 任意转换
s char* 以c0结尾的char*转换为Python字符串,或NULL转换为None。
s# char*+int C字符*和长度到Python字符串,或空到无。
u Py_UNICODE* c -宽,以NULL结尾的字符串为Python Unicode,或NULL为None。
u# Py_UNICODE*+int C-wide字符串和长度到Python Unicode,或NULL到None。
w# char*+int 读/写单段缓冲区到C地址和长度。
z char* 与s类似,也接受None(将C char*设置为NULL)。
z# char*+int 与s#类似,也接受None(将C char*设置为NULL)。
(…) as per … 从C值构建Python元组。
[…] as per … 从C值构建Python列表。
{…} as per … 从C值、交替键和值构建Python字典。

代码{…}从偶数个C值(交替使用键和值)构建字典,例如Py_BuildValue(“{issi}”,23,”zig”,”zag”,42)返回一个类似Python的{23:’zig’,’zag’:42}的字典。

赞(0)
未经允许不得转载:srcmini » python C语言扩展编程 – Python高级开发教程

评论 抢沙发

评论前必须登录!