Python API C 的实例 无缝调用

Python C API

在一些场景中,以 Python 开发可以高效的完成项目进度,但是一些功能在从网上复制粘贴或者需要C语言调用时,我们就需要用到 Python C API

优势

  • 不需要其他库
  • 大量低级控制
  • 完全可用的C++语言

缺点

  • 可能比较耗时耗神
  • 代码编写花费时间长
  • 必须编译
  • 维护成本高
  • 随着C-API的变化,Python 版本之间没有前向兼容性
  • 引用计数十分容易抛出异常并很难跟踪

环境

  • Anaconda3 2019.10
  • Clion

编写

本文以计算 Sin 正弦值为例,编写一个可以使用 python setup.py install 的 C 语言模板

示例, 创建文件 sin_module.c

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <Python.h>
#include <math.h>

/* 外层包裹 Sin 函数 */
static PyObject* sin_func(PyObject* self, PyObject* args)
{
double value;
double answer;

/* 解析用户输入从 Python 的 float 到 C 语言的 double */
if (!PyArg_ParseTuple(args, "d", &value))
return NULL;
/* 如果程序函数返回 -1, 会引发一个异常并返回 null */

/* 调用正弦值计算 */
answer = sin(value);

/* 构建 Sin 的计算结果, 从 C 的 double 到 python 的 float */
return Py_BuildValue("f", answer);
}

/* 定义模板中的函数 */
static PyMethodDef SinMethods[] =
{
{"sin_func", sin_func, METH_VARARGS, "evaluate the sine"},
{NULL, NULL, 0, NULL}
};

#if PY_MAJOR_VERSION >= 3
/* 模板的初始化 */
/* Python 版本 3*/
static struct PyModuleDef cModPyDem =
{
PyModuleDef_HEAD_INIT,
"sin_module", "documentation",
-1,
SinMethods
};

PyMODINIT_FUNC
PyInit_sin_module(void)
{
return PyModule_Create(&cModPyDem);
}

#else

/* 模板初始化 */
/* Python 版本 2 */
PyMODINIT_FUNC
initsin_module(void)
{
(void) Py_InitModule("sin_module", SinMethods);
}

#endif

创建 setup.py 文件:

1
2
3
4
5
6
7
from distutils.core import setup, Extension

# 定义扩展模板
sin_module = Extension('sin_module', sources=['sin_module.c'])

# 运行 setup
setup(ext_modules=[sin_module])

随后运行

script
1
python setup.py install

setup.py install

即可安装, 随后导入模板进行测试

script
1
2
3
4
5
python
import sin_module
print(sin_module.sin_func(180))

>> -0.8011526357338304

模板测试

Py_BuildValue() 返回值定义

  • “s” (string) [char *] : 将C字符串转换成Python对象,如果C字符串为空,返回 None
  • “s#” (string) [char *, int] : 将 C 字符串和它的长度转换成 Python 对象,如果 C 字符串为空指针,长度忽略,返回 None
  • “z” (string or None) [char *] : 作用同 “s”
  • “z#” (string or None) [char *, int] : 作用同 “s#”
  • “i” (integer) [int] : 将一个 C 类型的 int 转换成 Python int 对象
  • “b” (integer) [char] : 作用同”i”
  • “h” (integer) [short int] : 作用同”i”
  • “l” (integer) [long int] : 将 C 类型的 long 转换成 Python 中的int对象
  • “c” (string of length 1) [char] : 将 C 类型的 char 转换成长度为1的 Python 字符串对象
  • “d” (float) [double] : 将 C 类型的 double 转换成python中的浮点型对象
  • “f” (float) [float] : 作用同”d”
  • “O&” (object) [converter, anything] : 将任何数据类型通过转换函数转换成 Python 对象,这些数据作为转换函数的参数被调用并且返回一个新的 Python 对象,如果发生错误返回 null
  • “(items)” (tuple) [matching-items] : 将一系列的 C 值转换成 Python 元组
  • “[items]” (list) [matching-items] : 将一系列的 C 值转换成 Python 列表
  • “{items}” (dictionary) [matching-items] : 将一系类的 C 值转换成 Python 的字典,每一对连续的 C 值将转换成一个键值对

示例:

构建的值 返回值
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) 'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}","abc", 123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))

模板的属性定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from distutils.core import setup, Extension

example_module = Extension('_example', # 模块名称,和example.i中的%module对应加下划线
include_dirs=['../libpath1/include', # 头文件搜索路径,include搜索路径,多个路径逗号分隔
'../libpath2/include',
'../libpath3/include',
'./'], # 如果用到了当前目录下的其他头文件,则包含当前路径
library_dirs=['./'],
# 动态库搜索目录,linux下动态库文件搜索路径需要,由于本次使用的都是静态库所以不用,windows平台下所有库搜索路径都应该放到libraries里面,多个路径逗号分隔
libraries=[], # 使用到的so动态库名字。库文件都是以lib开头的,都是libxxxx,理论上只需要填写去掉lib的部分,也就是只写xxxx就行,多个库文件逗号分隔
sources=['example_wrap.cxx', 'example.cc', 'other-cpp-wrapper.cc'],
# 编译源文件,如果当前工程包含多个源文件必须把所有源文件都放进来,否则编译成功后在python中import时会报找不到other-cpp-wrapper.cc中的方法的连接错误。
extra_objects=['../libpath1/lib/lib1.a',
# 注意:Linux的用到的静态连接库应该放到这个参数中,填写完整路径才行,不能只填写库名称(和动态库有区别)
'../libpath1/lib/lib2.a',
'../libpath1/lib/lib3.a']
)
setup(name='example', # 模板名
version='0.1', # 版本
author='Arthur', # 作者名
description="""Simple test demo""", # 模板描述
ext_modules=[example_module],
py_modules=["example"],
)

更多参数
参数列表

官方文档位置