Python 简明教程

Python - Further Extensions

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

Python 扩展模块仅仅是标准 C 库。在 Unix 计算机上,这些库通常以 .so (针对共享对象)结尾。在 Windows 计算机上,您通常会看到 .dll (针对动态链接库)。

Pre-Requisites for Writing Extensions

若要开始编写扩展,您将需要 Python 头文件。

  1. 在 Unix 计算机上,这通常需要安装针对开发人员的特定程序包。

  2. Windows 用户在使用二进制 Python 安装程序后可获得这些标题,作为程序包的一部分。

此外,假定您对 C 或 C++ 有良好的了解,以便使用 C 编程编写任何 Python 扩展。

First look at a Python Extension

初次查看 Python 扩展模块,您需要将代码分成四个部分 −

  1. The header file Python.h.

  2. 您希望作为模块接口公开的 C 函数。

  3. 该表将用 Python 开发人员看到的方式映射函数名称,并将其作为扩展模块内的 C 函数映射。

  4. An initialization function.

The Header File Python.h

您需要在 C 源文件中包含 Python.h 头文件,以便您访问用于将模块连接到解释程序的内部 Python API。

请确保在您可能需要的任何其他头之前包含 Python.h。您需要使用要从 Python 调用的函数继续包含。

The C Functions

函数的 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 中的空函数。如果您不希望函数返回值,则返回 Python 的 None 值的 C 等效值。Python 标头定义了一个宏 Py_RETURN_NONE,它为我们执行此操作。

您可以随意指定 C 函数名称,因为它们永远不会在扩展模块外部看到。它们被定义为静态函数。

您通常会通过将 Python 模块和函数名称组合在一起来命名 C 函数,如下所示 −

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

这是模块内一个名为 func 的 Python 函数。您将在源代码中通常紧随其后的模块方法表中放置指向 C 函数的指针。

The Method Mapping Table

此方法表是一个 PyMethodDef 结构的简单数组。该结构类似于如下示例所示 −

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

以下是此结构中成员的描述 −

  1. ml_name − 这是 Python 解释器在 Python 程序中使用时显示的函数名称。

  2. ml_meth − 这是具有上一部分中描述的任何一个签名的函数的地址。

  3. ml_flags − 这告诉解释器 ml_meth 正在使用这三个签名中的哪一个。这个标志通常具有 METH_VARARGS 的值。如果你想允许你的函数中使用关键字参数,则可以使用 METH_KEYWORDS 按位或运算这个标志。它还可以具有 METH_NOARGS 值,表示你不想接受任何参数。

  4. mml_doc − 这是该函数的文档字符串,如果你不想编写一个,它可以是 NULL。

此表需要以包含相应成员的 NULL 和 0 值的哨兵终止。

Example

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

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

The Initialization Function

扩展模块的最后一部分是初始函数。当模块被载入时,Python 解释器将会调用此函数。该函数必须以 initModule 命名,其中 Module 为模块名称。

初始化函数需要从你将要构建的库导出。Python 头文件定义 PyMODINIT_FUNC,以便在我们要编译的特定环境中为其包含适当的咒语。你要做的就是用它来定义函数。

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

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

以下是 Py_InitModule3 函数的描述 −

  1. func − 这是要导出的函数。

  2. module_methods − 这是上面定义的映射表名称。

  3. 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...");
}

Example

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

#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 脚本调用它。

Building and Installing Extensions

distutils 包使得以一种标准的方式分发 Python 模块(纯 Python 模块和扩展模块)变得非常容易。模块以源代码形式分发,通常通过称为 setup.pyas 的设置脚本来构建和安装。

对于上述模块,你需要准备以下 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 上通常不是问题。

Importing Extensions

安装扩展程序后,你将能够在 Python 脚本中导入并调用该扩展程序,如下所示 −

import helloworld
print helloworld.helloworld()

这将产生以下 output

Hello, Python extensions!!

Passing Function Parameters

由于你很可能想要定义接受参数的函数,因此你可以对 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 函数从一个 PyObject 指针中提取参数,该指针传递到你的 C 函数中。

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 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)

你可能会想出更多变体。

The PyArg_ParseTuple Function

re 是 PyArg_ParseTuple 函数的标准签名 −

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

此函数在出错时返回 0,在成功时返回不等于 0 的值。Tuple 是 C 函数的第二个参数 PyObject*。此处 format 是一个 C 字符串,它描述了必需参数和可选参数。

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

Returning Values

Py_BuildValue 采用一个格式字符串,类似于 PyArg_ParseTuple。你不必输入正在构建的值的地址,而是输入实际值。以下是一个显示如何实现 add 函数的示例。

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,...)

此处 format 是一段 C 字符串,它描述要构建的 Python 对象。Py_BuildValue 的以下参数是 C 值,结果将基于 C 值构建。PyObject* result 是一个新的引用。

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

代码 {…​} 从偶数个 C 值构建字典,依次是键和值。例如,Py_BuildValue("{issi}",23,"zig","zag",42) 返回一个与 Python 中的 {23:'zig','zag':42} 类似的字典