C/C++ 编写 Python 扩展-返回值(2)

我们简单介绍下,C/C++ 编写的扩展函数,导入到 Python 中使用。步骤如下:

  1. 编写使用 Python/C API 编写 C/C++ 函数
  2. 编写 setup.py 文件
  3. 编译安装 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* 表示函数的返回类型,此时如果有返回值,可以按照下面的方式返回:
  1. 如果函数没有返回,可以使用宏 Py_RETURN_NONE 代替,表示函数返回 None;
  2. 如果函数返回其他值,可以使用 Py_BuildValue() 来构造返回值代替。

Py_BuildValue 第一个参数指定返回的类型,其他的类型如下:

Python 类型C/C++ 类型
sstrchar*
zstr/Nonechar*/NULL
iintint
llonglong
cstrchar
dfloatdouble
DcomplexPy_Complex
OanyPyObject*
SstrPyStringObject

接下来,我们得编写另外一个函数,来导出我们的 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': '男'}

未经允许不得转载:一亩三分地 » C/C++ 编写 Python 扩展-返回值(2)