Python C 扩展开发指南:构建高性能原生模块
使用 C、C++ 或 Java 等编译语言编写的代码,可集成或导入到 Python 脚本中,称为“扩展”。
Python 扩展模块本质是普通 C 库。Unix 下通常为 .so(共享对象),Windows 下为 .dll(动态链接库)。
编写扩展的前置条件
需要 Python 头文件:
- Unix:安装开发包如
python2.5-dev。 - Windows:二进制安装器自带头文件。
假设你具备良好 C/C++ 基础。
Python 扩展初探
扩展模块代码分四部分:
- 头文件
Python.h。 - 暴露给 Python 的 C 函数。
- 方法映射表:将 Python 可见函数名映射到 C 函数。
- 初始化函数。
头文件 Python.h
在 C 源文件包含 Python.h,获取内部 Python API 挂载模块到解释器。必须在其他头文件前包含。
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 函数,不想返回值用 Py_RETURN_NONE 宏返回 None。C 函数名可任意,定义为 static。习惯命名为 module_func 形式。
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
方法映射表
方法表是 PyMethodDef 结构体数组:
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
成员说明:
ml_name:Python 可见函数名。ml_meth:函数地址,符合上述签名之一。ml_flags:指示签名类型,通常METH_VARARGS;可按位或METH_KEYWORDS支持关键字参数;METH_NOARGS表示无参数。ml_doc:文档字符串,可为NULL。
表以 {NULL, NULL, 0, NULL} 哨兵结尾。
示例
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
初始化函数
模块加载时由解释器调用,函数名必须为 initModule(Module 为模块名)。需从库导出,使用 PyMODINIT_FUNC 宏。结构:
PyMODINIT_FUNC initModule() {
Py_InitModule3("func", module_methods, "docstring...");
}
Py_InitModule3 参数:func 导出函数,module_methods 映射表名,docstring 模块文档。
完整示例:
#include
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...");
}
完整实例:helloworld
#include
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,下面演示编译安装。
构建与安装扩展
distutils 包简化模块分发。通过 setup.py 脚本构建安装:
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
ext_modules=[Extension('helloworld', ['hello.c'])])
执行命令完成编译链接并复制动态库到 site-packages:
$ python setup.py install
Unix 需 root 权限写入 site-packages,Windows 通常无需。
导入扩展
安装后即可在 Python 脚本导入调用:
#!/usr/bin/python
import helloworld
print(helloworld.helloworld())
输出:
Hello, Python extensions!!
传递函数参数
定义接受参数的函数,使用带 args 的签名:
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}
};
使用 PyArg_ParseTuple 从 args 提取参数。第一参数为 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 函数详解
标准签名:
int PyArg_ParseTuple(PyObject* tuple, char* format, ...)
返回 0 表示错误,非 0 成功。tuple 为 C 函数第二参数。format 为 C 字符串描述必选/可选参数。格式代码表:
| 代码 | C 类型 | 含义 |
|---|---|---|
| c | char | 长度 1 的 Python 字符串转 C char |
| d | double | Python float 转 C double |
| f | float | Python float 转 C float |
| i | int | Python int 转 C int |
| l | long | Python int 转 C long |
| L | long long | Python int 转 C long long |
| O | PyObject* | 获取非空借用引用 |
| 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 |
| (...) | 按 ... 解析 | Python 序列视为每项一个参数 |
| | | 后续参数为可选 | |
| : | 格式结束,后跟函数名用于错误信息 | |
| ; | 格式结束,后跟完整错误信息文本 |
返回值
Py_BuildValue 接受类似 PyArg_ParseTuple 的格式字符串,但传入实际值而非地址。实现加法示例:
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a, 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, 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 函数详解
标准签名:
PyObject* Py_BuildValue(char* format, ...)
format 描述要构建的 Python 对象,后续参数为 C 值。返回新引用 PyObject*。常用格式代码:
| 代码 | C 类型 | 含义 |
|---|---|---|
| c | char | C char 转长度 1 Python 字符串 |
| 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 对象并正常 INCREF |
| O& | convert+void* | 任意转换 |
| s | char* | C 以空结尾字符串转 Python 字符串,NULL 转 None |
| s# | char*+int | C 字符串和长度转 Python 字符串,NULL 转 None |
| u | Py_UNICODE* | C 宽字符串转 Python Unicode,NULL 转 None |
| u# | Py_UNICODE*+int | C 宽字符串和长度转 Python Unicode,NULL 转 None |
| w# | char*+int | 读写单段缓冲区转 C 地址和长度 |
| z | char* | 同 s,接受 None |
| z# | char*+int | 同 s#,接受 None |
| (...) | 按 ... 解析 | 从 C 值构建 Python 元组 |
| [...] | 按 ... 解析 | 从 C 值构建 Python 列表 |
| {...} | 按 ... 解析 | 从 C 值构建 Python 字典,键值交替 |
{...} 从偶数个 C 值构建字典,键值交替。例如 Py_BuildValue("{issi}",23,"zig","zag",42) 返回类似 Python {23:'zig','zag':42} 的字典。
Python
- 精通 Python 面向对象编程:类、继承、封装与多态
- Python print() 函数完全指南:基础用法、空行打印与自定义结束符
- Python strip() 方法完全指南:去除首尾空白与自定义字符实战
- Python str.count() 完全指南:统计字符与子串出现次数实战
- Python format() 字符串格式化完全指南:占位符、格式化规范、类与字典应用
- Python 字符串 find() 方法完全指南:语法、参数、替代方案与计数技巧
- 精通 Python Lambda 函数:实用示例与最佳实践
- Python round() 函数详解与实战示例
- 精通 Python map() 函数:语法、示例与最佳实践
- Python timeit():精准测量代码执行时间与实战示例
- Python collections.Counter:高效计数、更新与算术运算
- Python list.count() 专家指南:语法、示例与性能解析