使用C进行Python3扩展编程


你使用任何编译语言(例如C,C ++或Java)编写的任何代码都可以集成或导入另一个Python脚本中。该代码被视为“扩展名”。

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

编写扩展的学习前提


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

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

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

此外,假定你具有C或C ++的丰富知识,可以使用C编程编写任何Python扩展。

首先看一下Python扩展


首先看一下Python扩展模块,你需要将代码分为四个部分:

  • 头文件 Python.h .

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

  • 一张表,映射了你作为Python开发人员的函数名称,将其视为扩展模块中的C函数。

  • 初始化函数。

头文件Python.h


你需要包括 Python.h C源文件中的头文件,该文件可让你访问用于将模块挂接到解释器的内部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对象。没有这样的事情 void Python中的函数与C中的函数一样。如果你不希望函数返回值,请返回Python的C等效项。 None 价值。 Python标头定义了一个宏Py_RETURN_NONE,它可以为我们执行此操作。

C函数的名称可以是你喜欢的任何名称,因为它们在扩展模块之外从未见过。它们被定义为 static 功能。

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

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* Do your stuff here. */
    Py_RETURN_NONE;
}

这是一个Python函数,称为 func 在模块内部 module 。你将把指向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。

    • 如果要允许关键字参数进入函数,可以将该标志与METH_KEYWORDS进行按位或运算。

    • 它也可以具有METH_NOARGS的值,指示你不想接受任何参数。

  • ml_doc :这是该函数的文档字符串,如果你不想编写一个字符串,则可以为NULL。

该表需要以一个由NULL和0值组成的适当成员的标记终止。

对于上面定义的功能,我们有以下方法映射表:

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

初始化功能


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

需要从将要构建的库中导出初始化函数。 Python标头定义了PyMODINIT_FUNC,以包含针对我们正在编译的特定环境而发生的适当提示。你需要做的就是在定义函数时使用它。

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

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

这是对 Py_InitModule3 功能:

  • func :这是要导出的功能。

  • module_methods :这是上面定义的映射表名称。

  • 文档字符串 :这是你想在扩展名中给出的注释。

将所有这些放在一起,如下所示:

#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!");
}

Here the Py_BuildValue 函数用于构建Python值。将以上代码保存在hello.c文件中。我们将看到如何编译和安装要从Python脚本调用的模块。

构建和安装扩展


The 发行版 软件包使以标准方式分发纯Python和扩展模块的Python模块变得非常容易。模块以源代码形式分发,通过通常称为的安装脚本来构建和安装 setup.py as.

对于以上模块,你需要准备以下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) {
    /* Parse args and do something interesting here. */
    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参数。这就是你将要成为的对象 parsing 。第二个参数是一个格式字符串,描述了你希望它们出现的参数。每个自变量由格式字符串中的一个或多个字符表示,如下所示。

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 interesting here. */
    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的值表示成功。元组是PyObject *,它是C函数的第二个参数。这里 format 是描述强制和可选参数的C字符串。

这是列表的格式代码 PyArg_ParseTuple 功能:

Code C type Meaning
c char 长度为1的Python字符串成为C字符。
d double Python浮点数变成C的双精度数。
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参数的非NULL借用引用。
s char* 不带C null *的null的Python字符串。
s# char * + int 任何Python字符串到C地址和长度。
t# char * + int 只读单段缓冲区的C地址和长度。
u Py_UNICODE * 不带C的null的Python Unicode。
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)

The Py_BuildValue Function


这是用于的标准签名 Py_BuildValue 功能:

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

Here format 是一个C字符串,描述了要构建的Python对象。以下论点 Py_BuildValue 是从中构建结果的C值。这 PyObject * 结果是一个新的参考。

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

Code C type Meaning
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对象,并像往常一样INCREFs。
O& 转换+无效* 任意转换
s char* C 0终止的char *到Python字符串,或者NULL到None。
s# char * + int C char *,长度为Python字符串,或NULL为None。
u Py_UNICODE * C范围的,以Null终止的字符串,转换为Python Unicode,或NULL,转换为None。
u# Py_UNICODE * + int C范围的字符串和长度为Python Unicode,或NULL为无。
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字典。

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