Python 3 – 用C进行扩展编程

Python 3 – 用C进行扩展编程

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

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

编写扩展的先决条件

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

  • 在Unix机器上,通常需要安装特定于开发人员的软件包,例如 python2.5-dev 。

  • Windows用户在使用二进制Python安装程序时将这些头文件作为软件包的一部分获取。

此外,假定你具有使用C编程编写任何Python扩展的良好知识。

首先看一下Python扩展

要查看Python扩展模块,你需要将你的代码分成四部分 –

  • 头文件 Python.h

  • 你要作为模块接口公开的C函数。

  • 将Python开发人员在扩展模块中看到的函数名称映射为C函数的表。

  • 初始化函数。

头文件Python.h

在C源文件中需要包含 Python.h 头文件,该文件给你访问内部Python API的权限,用于将你的模块挂钩到解释器中。

确保在任何其他必要的头文件之前包含Python.h。需要遵循使用你想要从Python调用的函数的头文件。

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函数的名称可以随意设置,因为它们从未在扩展模块之外看到。它们被定义为 static 函数。

通常,你的C函数由组合Python模块和函数名称而命名,如下所示 –

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* 在这里执行你的操作。 */
   Py_RETURN_NONE;
}

这是模块 module 中的一个Python函数 func 。你将把指向你的C函数的指针放入通常在你的源代码中接下来的模块的方法表中。

方法映射表

该方法表是一组简单的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的值。

    • 如果您想要允许关键字参数进入您的函数,则可以将此标志按位OR’ed METH_KEYWORDS。

    • 还可以具有值METH_NOARGS,表示您不希望接受任何参数。

  • ml_doc − 这是函数的docstring,如果您不想编写docstring,则可以为NULL。

此表需要以对应成员的NULL和0值为终止的sentinel终止。

示例

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

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

初始化函数

您的扩展模块的最后部分是初始化函数。当加载模块时,Python解释器将调用此函数。需要将函数命名为 **init Module ** ,其中 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) {
   /* Do your stuff here. */
   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 extensions!!");
}

static char helloworld_docs[] =
   "helloworld( ): Any message you want to put here!!\n";

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

void inithelloworld(void)
{
   Py_InitModule3("helloworld", helloworld_funcs, "Extension module example!");
}

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

构建和安装扩展

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的系统上,您很可能需要以root身份运行此命令,以便具有写入site-packages目录的权限。在Windows上,通常不会出现这个问题。

导入扩展

一旦安装了扩展,您就可以像以下示例那样在Python脚本中导入和调用扩展:

示例

#!/usr/bin/python3
import helloworld

print helloworld.helloworld()

输出

这将产生以下结果:

Hello, Python extensions!!

传递函数参数

由于您最有可能想要定义接受参数的函数,因此您可以使用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;
   }

   /* 在此执行一些有趣的操作。*/
   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)

您可能会想出更多变化。

PyArg_ParseTuple函数

下面是 PyArg_ParseTuple 函数的标准签名:

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

此函数返回错误时为0,成功时不为0。tuple是C函数的第二个参数的PyObject*。在这里, _format_ 是描述强制和可选参数的C字符串。

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

代码 C类型 含义
c char Python的长度为1的字符串变为C char类型。
d double Python的浮点数变为C double类型。
f float Python的浮点数变为C float类型。
i int Python的整数变为C int类型。
l long Python的整数变为C long类型。
L long long Python的整数变为C long long类型。
O PyObject* 获取Python参数的非NULL借用引用。
s char* Python的不含空字符的字符串变为C char*类型。
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)。
(...) 根据… 将Python序列视为每个项目一个参数。
| 下列参数为可选。
: 格式化结束,后跟函数名称以供错误消息使用。
; 格式化结束,后跟整个错误消息文本。

返回值

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)

Py_BuildValue 函数

这是 Py_BuildValue 函数的标准签名 −

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

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

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

Code C type Meaning
c char 一个C char变成Python字符串,长度为1.
d double 一个C double变成Python float.
f float 一个C float变成Python float.
i int 一个C int变成Python int.
l long 一个C long变成Python int.
N PyObject* 传递Python对象并获得引用。
O PyObject* 传递Python对象并以正常方式增加引用。
O& 转换+void* 任意转换
s char* C 0终止的char *转换成Python字符串,或空转换为None。
s# char*+int C char*和长度转换成Python字符串,或空转换为None。
u Py_UNICODE* C宽字符,以null结尾的字符串转换为Python Unicode,或空转换为None。
u# Py_UNICODE*+int C宽字符串和长度转换为Python Unicode,或空转换为None。
w# char*+int 从C地址和长度读/写单个段缓冲区。
z char* 像s一样,也接受None(将C char *设置为NULL)。
z# char*+int 像s#一样,也接受None(将C char *设置为NULL)。
(...) 象…… 从C值构建Python元组。
[...] 象…… 从C值构建Python列表。
{...} 象…… 从C值构建Python字典,交替键和值。

Code {…} 从偶数个C值交替生成键和值的Python字典。例如,Py_BuildValue(“{issi}”,23,”zig”,”zag”,42)返回类似Python的{23:’zig’,’zag’:42}的字典。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程