PythonQt 学习之旅(二):跨越鸿沟——C++ 与 Python 的数据流转

导语:在第一阶段的“Hello World”中,我们让 C++ 成功唤起了 Python 解释器。但真正的混合编程,绝不是 C++ 单方面地“自言自语”,而是两者之间流畅的数据交换。本篇将聚焦 基础数据类型、集合类型的双向传递,以及 外部脚本的加载与函数调用,带你彻底打通 C++ 与 Python 的任督二脉。


一、数据翻译官:理解 QVariant 与 Python 类型的映射

Python 是动态类型语言,C++ 是强类型语言。当数据跨越边界时,谁来当翻译?
答案就是:QVariant
PythonQt 在底层建立了一套自动映射机制。当 Python 把数据交给 C++ 时,会包装成 QVariant;C++ 读取时,再通过 toInt(), toString() 等方法拆包。

1.1 基础类型映射表

C++ 类型 (通过 QVariant)Python 类型相互转换示例
intintQVariant(42)42
double / floatfloatQVariant(3.14)3.14
boolboolQVariant(true)True
QStringstrQVariant("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 类型说明
QVariantListlist[1, 2, "a"]
QVariantMapdict{"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.pyimport 了其他自定义模块,可能会报错。解决办法是在 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_INVOKABLEQ_PROPERTY 给 Python 开后门。
  • 如何将一个 C++ 单例直接扔给 Python 用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值