Python - 用C扩展编程

使用任何编译语言(如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

您需要在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中没有像 void 那样的东西,如C中所有.如果你不希望你的函数返回一个值,则返回Python的C等价物值. Python标头定义了一个宏Py_RETURN_NONE,它为我们做了这个.

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

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

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

这是一个名为 func 的Python函数,位于模块 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对这个标志进行按位OR运算.

    • 这也可以是METH_NOARGS的值,表示你不想接受任何参数.

  • ml_doc : 这是函数的docstring,如果你不想写一个函数,它可能是NULL.

这个表需要终止具有由适当成员的NULL和0值组成的标记.

示例

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

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}
};

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

构建和安装扩展

distutils package使得以标准方式分发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/python
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参数.这是您将解析的对象.第二个参数是一个格式字符串,用于描述您希望它们出现的参数.每个参数由格式字符串中的一个或多个字符表示如下.

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 function :

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

此函数返回0表示错误,值不等于0表示成功.元组是PyObject *,它是C函数的第二个参数.这里格式是一个描述强制和可选参数的C字符串.

这是 PyArg_ParseTuple 函数&minus的格式代码列表;

CodeC type含义
cchar长度为1的Python字符串成为C字符.
ddoublePython float变为C double.
ffloatPython float变为C float.
iintPython int变为C int.
llongPython int变为C long.
Llong longPython int变为C long long
OPyObject *获取对Python参数的非NULL借用引用.
schar *没有嵌入空值到C char *的Python字符串.
s#char * + int任何Python字符串到C地址和长度.
t#char * + int只读单段缓冲区到C地址和长度.
uPy_UNICODE *没有嵌入空值的Python Unicode到C.
u#Py_UNICODE * + int任何Python Unicode C地址和长度.
w#char * + int读取/写入单段缓冲区为C地址和长度.
zchar *与s类似,也接受None(将C char *设置为NULL).
z#char * + int与s#一样,也接受None(将C char *设置为NULL).
(...)按...Python序列被视为每个项目的一个参数.
| 以下参数是可选的.
: 格式化结束,后跟错误消息的函数名称.
; 格式结束,然后是整个错误消息文本.

返回值

Py_BuildValu e 接收格式字符串,就像 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(a, b):
   return (a + b)

Py_BuildValue 函数

这是 Py_BuildValue 函数的标准签名 :

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

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

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

CodeC type含义
ccharAC char成为长度为1的Python字符串.
ddoubleAC double成为Python float.
ffloatAC float成为Python float.
iintAC int成为Python int.
llongAC long成为Python int.
NPyObject *传递一个Python对象并窃取一个引用.
OPyObject *传递一个Python对象并正常INCREF它.
O&convert + void *任意转换
schar *C 0终止char *到Python字符串,或NULL到None.
s#char * + intC char *和Python字符串的长度,或NULL到None.
uPy_UNICODE *C宽,以null结尾的字符串到Python Unicode,或NULL到无.
u#Py_UNICODE * + intC-wide字符串和长度为Python Unicode,或NULL为无.
w#char * + int读取/写入单段缓冲区为C地址和长度.
zchar *与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}.