导语:在第一阶段的“Hello World”中,我们让 C++ 成功唤起了 Python 解释器。但真正的混合编程,绝不是 C++ 单方面地“自言自语”,而是两者之间流畅的数据交换。本篇将聚焦 基础数据类型、集合类型的双向传递,以及 外部脚本的加载与函数调用,带你彻底打通 C++ 与 Python 的任督二脉。
一、数据翻译官:理解 QVariant 与 Python 类型的映射
Python 是动态类型语言,C++ 是强类型语言。当数据跨越边界时,谁来当翻译?
答案就是:QVariant。
PythonQt 在底层建立了一套自动映射机制。当 Python 把数据交给 C++ 时,会包装成 QVariant;C++ 读取时,再通过 toInt(), toString() 等方法拆包。
1.1 基础类型映射表
| C++ 类型 (通过 QVariant) | Python 类型 | 相互转换示例 |
|---|---|---|
int | int | QVariant(42) ↔ 42 |
double / float | float | QVariant(3.14) ↔ 3.14 |
bool | bool | QVariant(true) ↔ True |
QString | str | QVariant("Hello") ↔ "Hello" |
1.2 代码实战:基础变量读写
PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
// ===== C++ → Python:注入变量 =====
mainModule.addVariable("appId", 1001);
mainModule.addVariable("version", QString("2.0.1"));
mainModule.addVariable("isDebug", true);
// 在 Python 中验证
mainModule.evalScript("print(f'ID:{appId}, Ver:{version}, Debug:{isDebug}')");
// ===== Python → C++:读取变量 =====
mainModule.evalScript("appName = 'MySuperApp'"); // Python 创建变量
mainModule.evalScript("tax = 0.05");
QString name = mainModule.getVariable("appName").toString();
double tax = mainModule.getVariable("tax").toDouble();
qDebug() << "C++ read -> Name:" << name << ", Tax:" << tax;
二、集合类型的穿梭:List 与 Dict
只有基础类型是不够的,业务中大量使用列表和字典。PythonQt 完美支持了 Qt 容器与 Python 容器的互转。
2.1 集合映射表
| C++ 类型 | Python 类型 | 说明 |
|---|---|---|
QVariantList | list | 如 [1, 2, "a"] |
QVariantMap | dict | 如 {"name": "Alice", "age": 30} |
2.2 代码实战:集合传递
PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
// ===== C++ → Python:传递 List =====
QVariantList productList;
productList << "Laptop" << "Mouse" << "Keyboard";
mainModule.addVariable("products", productList);
// 在 Python 中操作 list
mainModule.evalScript(R"(
products.append("Monitor")
print(f"Products in Python: {products}")
)");
// ===== C++ → Python:传递 Dict =====
QVariantMap config;
config["host"] = "127.0.0.1";
config["port"] = 8080;
mainModule.addVariable("config", config);
// ===== Python → C++:读取 Dict =====
mainModule.evalScript("config['status'] = 'running'"); // Python 新增 key
QVariantMap updatedConfig = mainModule.getVariable("config").toMap();
qDebug() << "Host:" << updatedConfig["host"].toString();
qDebug() << "Status:" << updatedConfig["status"].toString(); // 读取 Python 新增的值
三、告别硬编码:加载外部 Python 脚本
把 Python 代码写成字符串塞在 C++ 里,既难维护又无法热更新。真正的用法是 C++ 负责骨架,Python 写成 .py 文件作为“外挂”脚本。
3.1 evalFile() 的使用
假设我们有一个业务脚本 business.py:
# business.py
import math
def calculate_circle_area(radius):
"""计算圆的面积"""
if radius < 0:
return -1
return math.pi * (radius ** 2)
# 模块级别的变量
company_name = "TechCorp"
在 C++ 中加载并读取:
PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
// ★ 加载外部文件
mainModule.evalFile("scripts/business.py");
if (PythonQt::self()->hadError()) {
qWarning() << "脚本加载失败!";
return;
}
// 读取脚本中的模块级变量
QString company = mainModule.getVariable("company_name").toString();
qDebug() << "Company:" << company; // 输出: TechCorp
⚠️ 避坑指南:
evalFile()默认不会处理 Python 的sys.path。如果business.py中import了其他自定义模块,可能会报错。解决办法是在 C++ 启动初期,手动把脚本目录加入 Python 的搜索路径:
mainModule.evalScript("import sys; sys.path.append('./scripts')");
四、灵魂交互:C++ 调用 Python 函数
加载脚本只是第一步,调用脚本中的函数并获取返回值,才是嵌入 Python 的核心诉求。
PythonQt 提供了极其优雅的 call() 方法。
4.1 代码实战:函数调用
接着上面的 business.py,我们在 C++ 中调用 calculate_circle_area 函数:
PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
mainModule.evalFile("scripts/business.py");
// ★ 核心API:call("函数名", 参数列表)
QVariant result = mainModule.call("calculate_circle_area", QVariantList() << 5.0);
if (result.isValid()) {
double area = result.toDouble();
qDebug() << "圆的面积是:" << area; // 输出: 78.539...
}
// 传入错误参数测试
QVariant errResult = mainModule.call("calculate_circle_area", QVariantList() << -2.0);
qDebug() << "错误参数返回:" << errResult.toDouble(); // 输出: -1
4.2 处理复杂返回值
Python 函数经常返回字典或列表,C++ 该怎么接?
修改 business.py,增加一个函数:
def get_user_info(user_id):
return {
"id": user_id,
"name": "Alice",
"roles": ["admin", "viewer"]
}
C++ 端接收:
QVariant retVar = mainModule.call("get_user_info", QVariantList() << 1001);
// 拆包 Dict
if (retVar.canConvert<QVariantMap>()) {
QVariantMap userInfo = retVar.toMap();
qDebug() << "User ID:" << userInfo["id"].toInt();
qDebug() << "User Name:" << userInfo["name"].toString();
// 继续拆包 List
QVariantList roles = userInfo["roles"].toList();
for (const QVariant& role : roles) {
qDebug() << " - Role:" << role.toString();
}
}
五、阶段验收:构建一个配置热更新的小引擎
让我们用本阶段学到的知识,写一个稍微有点实战意义的代码:C++ 程序运行中,修改 Python 配置文件,C++ 端立刻生效。
config.py:
# 可随时外部修改
window_title = "Default App"
window_size = [800, 600]
C++ 代码:
#include <QApplication>
#include <QDebug>
#include <QPushButton>
#include <PythonQt.h>
int main(int argc, char** argv) {
QApplication app(argc, argv);
PythonQt::init();
PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
// 1. 加载配置
mainModule.evalFile("config.py");
QString title = mainModule.getVariable("window_title").toString();
QVariantList size = mainModule.getVariable("window_size").toList();
// 2. 创建 Qt 界面
QPushButton btn(title);
btn.resize(size[0].toInt(), size[1].toInt());
btn.show();
// 3. 点击按钮时,重新读取配置(模拟热更新)
QObject::connect(&btn, &QPushButton::clicked, [&]() {
mainModule.evalFile("config.py"); // 重新执行文件,刷新变量
QString newTitle = mainModule.getVariable("window_title").toString();
QVariantList newSize = mainModule.getVariable("window_size").toList();
btn.setText(newTitle);
btn.resize(newSize[0].toInt(), newSize[1].toInt());
qDebug() << "Config reloaded!";
});
return app.exec();
}
现在,运行程序,在程序运行期间去修改 config.py 里的 window_title 保存,然后点击按钮,你会发现界面标题变了!这就是嵌入 Python 带来的灵活性。
下一步预告
现在,你已经能让 C++ 和 Python 像老朋友一样交换数据、互相调用了。但目前的交互还局限在“基础数据类型”和“独立函数”上。
在实际的 Qt 项目中,我们有一大堆用 C++ 写好的业务类(UserManager, Database 等),我们希望 Python 能直接操作这些 C++ 对象的方法和属性。
在 第三阶段:赋予 Python 魔法——暴露 C++ 类与对象 中,我们将探讨:
- Qt 元对象系统(MOC)在 PythonQt 中扮演的上帝角色。
- 如何用
Q_INVOKABLE和Q_PROPERTY给 Python 开后门。 - 如何将一个 C++ 单例直接扔给 Python 用。
:跨越鸿沟——C++ 与 Python 的数据流转&spm=1001.2101.3001.5002&articleId=161959785&d=1&t=3&u=5ce58a7e779745738be11c64445ba905)
234

被折叠的 条评论
为什么被折叠?



