我们简单介绍下,C/C++ 编写的扩展函数,导入到 Python 中使用。步骤如下:
- 编写使用 Python/C API 编写 C/C++ 函数
- 编写 setup.py 文件
- 编译安装 C/C++ 扩展程序
程序环境:MacOS Big Sur + Clion + PyCharm
https://docs.python.org/3.8/c-api/
1.编写 C/C++ 函数 {#title-0}
libtest.cpp 程序文件如下:
#include <string>
#include <iostream>
#include <stdlib.h>
#include "Python.h"
#include "structmember.h"
// 1. 返回 None
extern "C" PyObject* py_test_function1(PyObject *, PyObject *)
{
Py_RETURN_NONE;
}
// 2. 返回整数类型
extern "C" PyObject* py_test_function2(PyObject *, PyObject *)
{
return Py_BuildValue("i", 100);
}
// 3. 返回元组类型
extern "C" PyObject* py_test_function3(PyObject *, PyObject *)
{
// 创建包含3个元素的元组
PyObject* tuple = PyTuple_New(3);
PyTuple_SetItem(tuple, 0, Py_BuildValue("i", 10));
PyTuple_SetItem(tuple, 1, Py_BuildValue("i", 20));
PyTuple_SetItem(tuple, 2, Py_BuildValue("i", 30));
// return tuple;
return Py_BuildValue("iii", 10, 20, 30);
// 等价于 (10, 20, 30)
// Py_BuildValue("((ii)(ii)) (ii)", 10, 20, 30, 40, 50, 60)
// 等价于 ((10, 20), (30, 40)), (50, 60))
// Py_BuildValue("[i,i]", 10, 20)
// 等价于 [10, 20]
// Py_BuildValue("ss", "aaa", "bbb")
// 等价于 ('aaa', 'bbb')
// Py_BuildValue("s#", "abcde", 4)
// 等价于 'abcd'
// Py_BuildValue("")
// 等价于 None
}
// 4. 返回字典
extern "C" PyObject* py_test_function4(PyObject *, PyObject *)
{
// 创建包含3个元素的元组
PyObject* dict = PyDict_New();
PyDict_SetItemString(dict, "Name", Py_BuildValue("s", "张三"));
PyDict_SetItemString(dict, "Age", Py_BuildValue("i", 18));
PyDict_SetItemString(dict, "Gender", Py_BuildValue("s", "男"));
// return dict;
return Py_BuildValue("{s:i,s:i,s:i}", "Name", "张三", "Age", 18, "Gender", "男");
// 等价于 {'Name': '张三', 'Age': 18, 'Gender': '男'}
}
// 定义导出模块信息
PyMODINIT_FUNC PyInit_libtest()
{
static PyMethodDef py_methods[] =
{
{"test_func1", py_test_function1, METH_VARARGS, "测试函数"},
{"test_func2", py_test_function2, METH_VARARGS, "测试函数"},
{"test_func3", py_test_function3, METH_VARARGS, "测试函数"},
{"test_func4", py_test_function4, METH_VARARGS, "测试函数"},
{NULL, NULL, NULL, NULL}
};
static PyModuleDef py_modules =
{
PyModuleDef_HEAD_INIT,
"libtest",
NULL,
-1,
py_methods
};
return PyModule_Create(&py_modules);
}
上面的函数是我们最终要导出到 Python 中使用的函数,由于需要用到 Python/C 的一些接口,所以导入两头文件:
#include "Python.h"
#include "structmember.h"
由于 C++ 支持函数重载,编译之后的函数名会进行修饰,和 C 语言函数名的修饰方式不同,所以需要加载 extern C,使得 C++ 函数按照 C 的方式修饰。
PyObject* 表示函数的返回类型,此时如果有返回值,可以按照下面的方式返回:
- 如果函数没有返回,可以使用宏 Py_RETURN_NONE 代替,表示函数返回 None;
- 如果函数返回其他值,可以使用 Py_BuildValue() 来构造返回值代替。
Py_BuildValue 第一个参数指定返回的类型,其他的类型如下:
|---|-----------|----------------| | | Python 类型 | C/C++ 类型 | | s | str | char* | | z | str/None | char*/NULL | | i | int | int | | l | long | long | | c | str | char | | d | float | double | | D | complex | Py_Complex | | O | any | PyObject* | | S | str | PyStringObject |
接下来,我们得编写另外一个函数,来导出我们的 C/C++ 函数,代码如下:
PyMODINIT_FUNC PyInit_libtest()
{
static PyMethodDef py_methods[] =
{
{"test_func1", py_test_function1, METH_VARARGS, "测试函数"},
{"test_func2", py_test_function2, METH_VARARGS, "测试函数"},
{"test_func3", py_test_function3, METH_VARARGS, "测试函数"},
{"test_func4", py_test_function4, METH_VARARGS, "测试函数"},
{NULL, NULL, NULL, NULL}
};
static PyModuleDef py_modules =
{
PyModuleDef_HEAD_INIT,
"libtest",
NULL,
-1,
py_methods
};
return PyModule_Create(&py_modules);
}
- 编写 setup.py 文件 {#title-1} ============================
#setup.py
from distutils.core import setup, Extension
module_name = 'libtest'
setup(name=module_name,ext_modules=[
Extension(
module_name,
sources=['libtest.cpp'],
extra_compile_args=['-Wall', '-g'],
include_dirs=['/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8/']
)])
- 编译安装扩展模型 {#title-2} ======================
python setup.py build
python setup.py install
接下来,我们在 Python 使用该模块,代码如下:
test.py
import libtest
print(libtest.test_func1())
print(libtest.test_func2())
print(libtest.test_func3())
print(libtest.test_func4())
程序输出结果:
None
100
(10, 20, 30)
{'Name': '张三', 'Age': 18, 'Gender': '男'}