使用编译语言如 C/C++ 或 Java 编写的代码可以集成或导入到另一个 Python 脚本中。这样的代码被视为“扩展”。
一个 Python 扩展模块实际上就是一个普通的 C 库。在 Unix 机器上,这些库通常以 .so(共享对象)结尾。而在 Windows 机器上,你通常看到的是 .dll(动态链接库)。
编写扩展的前提条件
为了开始编写你的扩展,你需要 Python 的头文件。
在 Unix 机器上,这通常要求安装一个开发者专用的包。
Windows 用户在使用二进制 Python 安装包时会得到这些头文件。
此外,假设你拥有足够的 C 或 C++ 知识来用 C 编程来编写任何 Python 扩展。
第一次查看 Python 扩展
为了第一次查看 Python 扩展模块,你需要将你的代码分为四部分:
-
-
-
一个函数名映射表,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 函数的名字可以是你喜欢的任何名字,因为它们永远不会出现在扩展模块之外。它们被定义为静态函数。
你的 C 函数通常通过将 Python 模块和函数名组合在一起命名,如下面所示:
static PyObject *module_func(PyObject *self, PyObject *args) {
Py_RETURN_NONE;
}
这是一个在模块 module 中被叫做 func 的 Python 函数。你会把指向你的 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 解释器调用。函数必须命名为 initModule
,其中 Module
是模块的名字。
初始化函数需要从你将构建的库中导出。Python 头文件定义了 PyMODINIT_FUNC
来包含特定环境中编译所需的适当魔法。你只需要在定义函数时使用它。
你的 C 初始化函数通常具有以下整体结构:
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
这是 Py_InitModule3
函数的描述:
-
-
module_methods
— 这是在上面定义的方法映射表的名字。
-
docstring
— 这是你想在扩展中给出的注释。
把这些放在一起,看起来像这样:
#include <Python.h>
static PyObject *module_func(PyObject *self, PyObject *args) {
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 脚本中导入并调用那个扩展:
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,在成功时返回非零值。tuple
是 C 函数的第二个参数的 PyObject* 类型。format
是一个 C 字符串,用来描述必选和可选的参数。
以下是一些 PyArg_ParseTuple
函数的格式代码列表:
代码 |
C 类型 |
含义 |
c |
char |
一个长度为 1 的 Python 字符串变为 C 字符。 |
d |
double |
Python 浮点数变为 C 双精度浮点数。 |
f |
float |
Python 浮点数变为 C 单精度浮点数。 |
i |
int |
Python 整数变为 C 整数。 |
l |
long |
Python 整数变为 C 长整型。 |
L |
long long |
Python 整数变为 C 长长整型。 |
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 字符指针设为 NULL)。 |
z# |
char*+int |
类似于 s#,也接受 None(将 C 字符指针设为 NULL)。 |
(…) |
as per ... |
Python 序列被视作每项一个参数。 |
|
|
下列参数是可选的。 |
: |
Format end, followed by function name for error messages. |
|
; |
Format end, followed by entire error message text. |
|
返回值
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
是一个 C 字符串,用来描述要构建的 Python 对象。Py_BuildValue
的后续参数是从 C 值构造的结果。PyObject*
结果是一个新的引用。
下表列出了常用代码字符串,零个或多个可以连接成字符串格式:
代码 |
C 类型 |
含义 |
c |
char |
C 字符变为长度为 1 的 Python 字符串。 |
d |
double |
C 双精度浮点数变为 Python 浮点数。 |
f |
float |
C 单精度浮点数变为 Python 浮点数。 |
i |
int |
C 整型变为 Python 整数。 |
l |
long |
C 长整型变为 Python 整数。 |
N |
PyObject* |
传递 Python 对象并偷取引用。 |
O |
PyObject* |
传递 Python 对象并 INCREF 它。 |
O& |
convert+void* |
任意转换 |
s |
char* |
C 以空字符终止的字符指针到 Python 字符串,或者 NULL 到 None。 |
s# |
char*+int |
C 字符指针和长度到 Python 字符串,或者 NULL 到 None。 |
u |
Py_UNICODE* |
C-wide,以空字符终止的字符串到 Python Unicode,或者 NULL 到 None。 |
u# |
Py_UNICODE*+int |
C-wide 字符串和长度到 Python Unicode,或者 NULL 到 None。 |
w# |
char*+int |
可读写单段缓冲区到 C 地址和长度。 |
z |
char* |
类似于 s,也接受 None(设置 C 字符指针为 NULL)。 |
z# |
char*+int |
类似于 s#,也接受 None(设置 C 字符指针为 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}
的字典。