C/C++与Python混合编程详解:全面解析你所需要知道的一切

Python与C++混合编程可以实现两种语言的优势结合,C++的程序性能很高且支持强大的系统调用能力,Python则生态丰富且开发效率高。本章将基于Python3讲述Python与C++混合编程的技术。

1. Python简介

1.1. 什么是Python?

Python是一种高级编程语言,具有简洁易读的语法和强大的功能。它于 1991 年由 Guido van Rossum 首次发布,快速发展成为一种广泛使用的编程语言。它是一种动态脚本语言,崇尚优美、清晰、简单的语法。

Python具有以下一些特性:

  • 跨平台:语言级别跨平台,几乎可以在各个平台间无缝切换,如 Windows、macOS 和 Linux等。
  • 生态丰富:拥有众多的标准库和第三方库,且拥有优秀的包管理机制。
  • 多范式编程: 支持多种编程范式,包括面向对象编程、过程式编程和函数式编程。
  • 应用广泛:Python 被广泛应用于科学计算、数据分析、人工智能、网络开发、自动化测试等领域。
  • 1.2. 常见数据类型

    Python是一种动态语言,变量的定义不需要在前面加类型说明,而且不同类型之间可以方便地相互转换。如下示例代码:

    a = "124"
    print("a:", a, "type:", type(a))
    b = int(a)
    print("b:", b, "type:", type(b))
    

    执行结果:

    a: 124 type: <class 'str'>
    b: 124 type: <class 'int'>
    

    Python3中有六个标准的数据类型:

    1. Numbers(数字)
    2. String(字符串)
    3. List(列表)
    4. Tuple(元组)
    5. Dictionary(字典)
    6. Set(集合)

    其中List、Tuple、Dictionary、Set为容器。Python支持四种不同的数字类型:int(有符号整型)、float(浮点型)、bool(布尔型)、complex(复数)。(说明:Python3中已去除long类型,与int类型合并)。

    Python具有以下常用的数据类型转换函数:

    函数 描述
    int(x [,base]) 将x转换为一个整数。base为可选参数,表示进制数,默认十进制。
    float(x) 将x转换到一个浮点数
    complex(real [,imag]) 创建一个复数。imag为可选参数,表示虚数部分
    str(x) 将对象 x 转换为字符串
    tuple(s) 将序列 s 转换为一个元组
    list(s) 将序列 s 转换为一个列表
    set(s) 转换为可变集合
    dict(d) 创建一个字典。d 必须是一个 (key, value)元组序列。
    frozenset(s) 转换为不可变集合
    chr(x) 将一个整数转换为一个字符
    ord(x) 将一个字符转换为它的整数值
    hex(x) 将一个整数转换为一个十六进制字符串
    oct(x) 将一个整数转换为一个八进制字符串

    1.3. 环境说明

    本章所有的示例代码都是使用Python3进行编写,具体的环境如下:

  • 操作系统: Ubuntu 24.04
  • Python: 3.12.3
  • 开发工具:VSCode
  • 2. 开发环境搭建

    2.1. Windows

    1. 下载最新版本的安装包,官网下载地址: https://www.python.org/downloads/。
    2. 点击安装包,根据提示一步一步操作即可,只需要注意以下两点:
    3. 勾选Add python.exe to PATH 将Python添加到环境变量
      file
    4. 如果要自定义安装路径,第一步需要选择Customize installation(如上图所示),然后Advaned options页面选择对于的安装路径(如下图)。
      file
    5. 安装完后打开命令提示符,然后输入python -V,如果显示对应的版本号,则说明安装成功。

    2.2. Linux(Ubuntu)

    # 1. 更新软件包列表
    sudo apt update
    # 2. 安装python3
    sudo apt install python3
    # 3. 验证python3是否安装成功,如果显示对应的版本号则说明安装成功。
    python3 -V
    # 4. 安装python3开发包
    sudo apt install python3-dev
    # 验证python3-dev是否安装成功,如果能看到相关的包信息则说明安装成功。
    dpkg -l | grep python3-dev
    

    2.3. macOS

    # 前期准备:安装Homebrew
    # 执行`brew -v`检查Homebrew是否安装,如果未安装,执行以下命令安装
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
    # 1. 更新软件包列表
    brew update
    # 2. 安装python
    brew install python
    # 3. 验证版本号,如何显示对应的版本号则说明安装成功。
    python3 -V
    

    3. Python/C API

    3.1. 什么是Python/C API?

    Python/C API是Python官方提供的一套API接口,允许开发者使用 C/C++ 语言来扩展Python。这个接口使得开发者可以在 C/C++ 语言中编写模块,这些模块可以被 Python 程序调用,从而执行更高效的计算,或者访问操作系统级别的资源。Python/C API也支持在C/C++中调用Python的模块代码,从而实现跨语言的混合编程。

    官方文档:https://docs.python.org/3.8/c-api/index.html#c-api-index

    3.2. C++调用Python模块

    py_math.py:

    def add(a: int, b: int):
        res = a + b
        return res
    
    def sub(a: int, b: int):
        return a - b
    

    cpp_call_python.cpp:

    #include <Python.h>
    #include <cstdint>
    #include <iostream>
    
    int32_t add_from_python(int32_t a, int32_t b, int32_t& res)
    {
        PyObject* pModuleName = PyUnicode_FromString("py_math");
        if (!pModuleName)
        {
            PyErr_Print();
            return -1;
        }
        // 导入模块
        PyObject* pModule = PyImport_Import(pModuleName);
        if (!pModule)
        {
            PyErr_Print();
            Py_DECREF(pModuleName);
            return -1;
        }
    
        /* great_module.great_function */
        PyObject* pFunc = PyObject_GetAttrString(pModule, "add");
        if (!pFunc || !PyCallable_Check(pFunc))
        {
            PyErr_Print();
            Py_DECREF(pModule);
            Py_DECREF(pModuleName);
            return -1;
        }
    
        // 设置参数
        PyObject* pArgs = PyTuple_Pack(
            2, 
            PyLong_FromLong(a), 
            PyLong_FromLong(b)
        );
        if (!pArgs)
        {
            PyErr_Print();
            Py_DECREF(pFunc);
            Py_DECREF(pModule);
            Py_DECREF(pModuleName);
            return -1;
        }
    
        // 调用函数
        PyObject* pValue = PyObject_CallObject(pFunc, pArgs);
        if (!pValue)
        {
            PyErr_Print();
            Py_DECREF(pFunc);
            Py_DECREF(pModule);
            Py_DECREF(pModuleName);
            return -1;
        }
    
        // 获取结果
        res = PyLong_AsLong(pValue);
        Py_DECREF(pValue);
        Py_DECREF(pFunc);
        Py_DECREF(pModule);
        Py_DECREF(pModuleName);
        return 0;
    }
    
    int main()
    {
        // 1. 初始化 Python 解释器
        Py_Initialize();
        // 2. 调用 Python 代码
        int32_t result = 0;
        auto ret = add_from_python(3, 2, result);
        if (ret < 0)
        {
            std::cerr << "add_from_python error ret:" << ret << std::endl;
        }
        else
        {
            std::cout << "3 + 2 = " << result << std::endl;
        }
        // 3. 结束 Python 解释器
        Py_Finalize();
        return 0;
    }
    

    编译和执行:

    # 编译
    g++ -g cpp_call_python.cpp -o cpp_call_python -I /usr/include/python3.12 -lpython3.12
    # 运行
    ./cpp_call_python 
    3 + 2 = 5
    

    常见问题和解决策略:

    如果出现找不到module的错误:ModuleNotFoundError: No module named 'py_math。需要设置PYTHONPATH环境变量,设置方法如下:

    # 1. vim打开.zshrc(如果你的SHELL用的是.bashrc,替换成相应的.bashrc)
    vim ~/.zshrc
    # 2. 在文件末尾添加如下内容
    export PYTHONPATH=.:$PYTHONPATH
    # 3. 重新加载配置
    source ~/.zshrc
    

    上面第2步表示将程序执行的当前目录加到PYTHONPATH环境变量中。

    3.3. Python调用C++模块

    cpp_math.cpp:

    #include <Python.h>
    #include <cstdint>
    #include <iostream>
    
    int32_t add(int32_t a, int32_t b)
    {
        return a + b;
    }
    
    static PyObject* _add(PyObject* self, PyObject* args)
    {
        int32_t _a;
        int32_t _b;
        int32_t res;
    
        // 将Python的参数转换为C++的参数
        if (!PyArg_ParseTuple(args, "ii", &_a, &_b))
            return NULL;
        // 调用add函数
        res = add(_a, _b);
        // 将C++的返回值转换为Python的返回值
        return PyLong_FromLong(res);
    }
    
    static PyMethodDef MathModuleMethods[] = { { "add", _add, METH_VARARGS, "" },
                                               { NULL, NULL, 0, NULL } };
    
    static struct PyModuleDef MathModule = { PyModuleDef_HEAD_INIT,
                                             "cpp_math", // 模块名称
                                             NULL,       // 模块文档
                                             -1,         // 模块状态
                                             MathModuleMethods };
    
    // (C扩展)模块初始化
    PyMODINIT_FUNC PyInit_cpp_math(void)
    {
        // 创建模块对象
        return PyModule_Create(&MathModule);
    }
    

    python_call_cpp.py:

    from cpp_math import add
    
    print("3 + 2 =", add(3, 2))
    

    编译和执行:

    # 编译
    g++ -fPIC -shared cpp_math.cpp -o cpp_math.so -I /usr/include/python3.12 -lpython3.12
    # 运行
    python ./python_call_cpp.py 
    3 + 2 = 5
    

    关键代码说明:

    PyArg_ParseTuple的用法

  • 第一个参数:是Python的参数列表,是一个Tuple类型。
  • 第二个参数:是一个字符串,表示要解析的参数的数据类型,有几个参数就写几个标识符,如:有int和float两个参数,就写"if""。数据类型标识和类型的对应关系如下:
  • i:表示将参数解析为 int 类型
  • f:表示将参数解析为 float 类型
  • s:表示将参数解析为 char*(字符串)类型
  • O:表示将参数解析为 PyObject* 类型
  • 之后的参数:第二个参数之后的是可变参数,填写对应的参数值或变量。
  • 3.4. 数据类型转换

    Python/C API提供了一系列用于Python和C/C++之间数据类型转换的函数,它们之间的对应关系如下:

    Python –> C/C++ C/C++ –> Python 备注
    PyLong_AsLong PyLong_FromLong 对应C++long类型的转换
    PyLong_AsUnsignedLong PyLong_FromUnsignedLong 对应C++ unsigned long类型的转换
    PyUnicode_AsUTF8AndSize PyUnicode_FromString 对应C++ const char *类型的转换
    PyFloat_AsDouble PyFloat_FromDouble 对应C++ double类型的转换

    历史文章推荐:

    01. 什么是SDK

    02. SDK的设计目标

    03. 接口设计与规范

    04. 接口注释与接口文档

    05. 原理篇:字符集与字符编码(一)

    06. 原理篇:字符集与字符编码(二)

    07. 原理篇:多字节字符与宽字节字符

    08. 原理篇:静态库、动态库与运行库

    09. 跨平台:C++标准的版本

    10. 跨平台:源码的保存格式与中文乱码问题

    11. 跨平台:宏定义隔离平台差异

    12. 跨平台:基础数据类型的定义

    13. 跨平台:文件系统的操作

    14. 跨平台:头文件包含的差异

    15. 跨平台:导出接口的定义

    16. 跨平台:字节序大端与小端

    17. 跨平台:内存和资源管理

    18. 工程篇:C/C++常用编译器

    19. 工程篇:用VSCode搭建C++开发环境

    20. 工程篇:CMake实现跨平台构建

    21. 工程篇:VSCode中使用CMake插件运行和调试程序

    22. 跨语言:跨语言的混合编程

    23. 跨语言:C++接口设计和代码实现

    24. 跨语言:C语言接口设计和代码实现

    25. 跨语言:C/C++与Python混合编程(一)

    26. 跨语言:C/C++与Python混合编程(二)

    附录A-计算机术语中成对出现的单词

    附录B: 计算机术语中常见的单词缩写

    作者:陌尘(MoChen)

    物联沃分享整理
    物联沃-IOTWORD物联网 » C/C++与Python混合编程详解:全面解析你所需要知道的一切

    发表回复