使用C语言为python添加简单的扩展

环境要求:C/C++编译器,gcc或者MSVC均可

参考自官方文档

使用python-api编写c语言代码

首先写一个简单的参数传递函数args_show()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <Python.h> // 必须引入Pyhton头文件才可以使用python-api
#include <stdio.h>

static PyObject* // PyObject对应python万物皆对象
args_show(PyObject *self, PyObject *args, PyObject *kwargs){
char* a;
char* b;
char *foo = "default foo";
char *bar = "default bar";
char *baz = "default baz";
// 关键字列表,对应def args_show(a, b, foo, bar, baz)
static char* kwlist[] = {"a", "b", "foo", "bar", "baz", NULL};
// 解析关键字参数,| 后为可选参数,解析失败返回NULL
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|sss", kwlist,
&a, &b, &foo, &bar, &baz))
{
Py_RETURN_NONE;
}

printf("a is %s\n", a);
printf("b is %s\n", b);
printf("foo is %s\n", foo);
printf("bar is %s\n", bar);
printf("baz is %s\n", baz);

Py_RETURN_NONE;
}

使用Python-api对函数进行封装以及注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 将c函数注册为python函数
// PyMethodDef为一个列表, 每个元素中包含四个字段:
// 注册的python的函数名,对应的c函数,参数接受方法,函数文档
static PyMethodDef KeywodsMethod[] = {
{"args_show", (PyCFunction)(void(*)(void))args_show,
// METH_VARARGS|METH_KEYWORDS 表示接收位置参数和关键字参数
METH_VARARGS|METH_KEYWORDS, "pratice kwargs"},
// 最后一项为固定值
{NULL, NULL, 0, NULL}
};

// 将函数列表注册到python模块
static struct PyModuleDef testmodule = {
PyModuleDef_HEAD_INIT,
"test", // 模块名__name__, 与import xxx无关
"some docs", // 模块文档
-1,
KeywodsMethod, // 模块中的方法列表
};

// 注册模块,使用PyInit_xxx进行初始化,xxx为导入时的模块名
PyMODINIT_FUNC
PyInit_test(void){
PyObject *m;

m = PyModule_Create(&testmodule);
if (m == NULL)
return NULL;

return m;
}

使用setup.py对模块进行编译

from setuptools import Extension, setup

module = Extension(
"test", # 模块名,即import test,必须与c文件中PyInit_xxx一致
sources=["./test.c"] # 模块对应的源文件(们)
)

setup(
ext_modules=[module]
)

之后使用python setup.py build_ext --inplace对源文件进行编译。编译后会在当前目录看到一个test.cpxx-xxx_xxx.pyd文件(xxx为编译平台信息)。因为使用了--inplace,如果模块名为xxx.test,编译后的文件会在xxx模块目录下生成。

测试

相同目录下新建一个测试文件导入方法进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from test import args_show
args_show("1", "2")
# a is 1
# b is 2
# foo is default foo
# bar is default bar
# baz is default baz

args_show("1", "2", "foo", "bar")
# a is 1
# b is 2
# foo is foo
# bar is bar
# baz is default baz

由于自己编写的C拓展模块没有对应的类型注解,可以自己在pyd同目录下编写一个同名的pyi文件用于类型注解

# filename test.pyi
def args_show(a: str,
b: str,
foo: str = "default foo",
bar: str = "default bar",
baz: str = "default baz") -> None :
...