Python与C++混合编程可以实现两种语言的优势结合,C++的程序性能很高且支持强大的系统调用能力,Python则生态丰富且开发效率高。本章将基于Python3讲述Python与C++混合编程的技术。
Python是一种高级编程语言,具有简洁易读的语法和强大的功能。它于 1991 年由 Guido van Rossum 首次发布,快速发展成为一种广泛使用的编程语言。它是一种动态脚本语言,崇尚优美、清晰、简单的语法。
Python具有以下一些特性:
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中有六个标准的数据类型:
其中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) | 将一个整数转换为一个八进制字符串 |
本章所有的示例代码都是使用Python3进行编写,具体的环境如下:
python -V
,如果显示对应的版本号,则说明安装成功。# 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
# 前期准备:安装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
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
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
#include
#include
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
环境变量中。
cpp_math.cpp:
#include
#include
#include
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/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: 计算机术语中常见的单词缩写