我们简单介绍下,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++ 函数
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); }
2. 编写 setup.py 文件
#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/'] )])
3. 编译安装扩展模型
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': '男'}