Qt调用python

Qt调用python,实际上就是c++调python,网上搜会出来很多,介绍得也比较全。这里做个记录

安装及使用

安装python,官网下载,按自己的需要是py2还是py3,是32位还是64位,这里就不多介绍了

安装完后找到安装目录,在pro文件链接py库

INCLUDEPATH += C:/Users/xx/AppData/Local/Programs/Python/Python39/include

LIBS += -LC:/Users/xx/AppData/Local/Programs/Python/Python39/libs -lpython39

由于是Qt上使用,简单封装成一个类,方便直接调用。

封装后的类如下:

#ifndef RDEXCUTEPYSCRIPT_H
#define RDEXCUTEPYSCRIPT_H

#undef slots
#include 
#define slots Q_SLOTS
#include 
/**
* @className   RdExcutePyScript
* @brief       执行py类
* @author      song
* @date        2021-07-23
*/

class RdExcutePyScript
{
public:
    explicit RdExcutePyScript(const char *module);
    ~RdExcutePyScript();

    bool callPyFunc(const char *func, const QVariantList &args = QVariantList(), QVariant *backVar = nullptr);

private:
    PyObject *m_pModule = nullptr;

};

#endif // RDEXCUTEPYSCRIPT_H
#include "rdexcutepyscript.h"
#include 
#include 

RdExcutePyScript::RdExcutePyScript(const char *module)
{
    //设置py home 打包用
//    char homePath[] = "python_27_32";
//    Py_SetPythonHome(homePath);
    //进行初始化
    Py_Initialize();

    //设置.py文件的路径,不设置在程序生成目录下可以找到,当前设置./,在该文件下的.py文件就能够识别到
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");

    //如果初始化失败,返回
    if(!Py_IsInitialized()) {
        qDebug() << __FUNCTION__ << "song" << "py init fail";
        return;
    }

    //加载模块,模块名称为myModule,就是myModule.py文件
    m_pModule = PyImport_ImportModule(module);

}

RdExcutePyScript::~RdExcutePyScript()
{
    Py_Finalize();
}

bool RdExcutePyScript::callPyFunc(const char *func, const QVariantList &args, QVariant *backVar)
{
    if(m_pModule == nullptr) {
        qDebug() << __FUNCTION__ << "song" << "module is null";
        return false;
    }
    //加载函数greatFunc
    PyObject * pFuncHello = PyObject_GetAttrString(m_pModule, func);
    //如果失败则返回
    if(!pFuncHello) {
        qDebug() << __FUNCTION__ << "song" << "load function fail";
        return false;
    }

    PyObject* pArgs = PyTuple_New(args.size());
    for(int i = 0; i < args.size(); ++i) {
        QVariant arg = args.at(i);
        switch (arg.type()) {
        case QVariant::String:
        {
            QString str = arg.toString();
            std::string str2 = str.toStdString();
            const char *ch = str2.c_str();
            PyTuple_SetItem(pArgs, i, Py_BuildValue("s", ch));
        }
            break;
        case QVariant::Int:
            PyTuple_SetItem(pArgs, i, Py_BuildValue("i", arg.toInt()));
            break;
        case QVariant::Double:
            PyTuple_SetItem(pArgs, i, Py_BuildValue("d", arg.toDouble()));
            break;

        default:
            break;
        }
    }

    //调用函数
    auto pReturn = PyObject_CallObject(pFuncHello, pArgs);

    if(backVar) {
        switch (backVar->type()) {
        case QVariant::String:
        {
            char *s = nullptr;
            PyArg_Parse(pReturn, "s", &s);
            QString str(s);
            *backVar = QVariant::fromValue(str);
        }
            break;
        case QVariant::Int:
        {
            int nResult;
            PyArg_Parse(pReturn, "i", &nResult);
            *backVar = QVariant::fromValue(nResult);
        }
            break;
        case QVariant::Double:
        {
            double dResult;
            PyArg_Parse(pReturn, "d", &dResult);
            *backVar = QVariant::fromValue(dResult);
        }
            break;
        default:
            break;
        }
    }

    return pReturn != nullptr;
}

调用

#include 
#include 
#include "rdexcutepyscript.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    RdExcutePyScript pyExcute("hello");
    bool rc = pyExcute.callPyFunc("hello");

    QVariant var = 0;
    QVariantList args = {1, 2};
    rc = pyExcute.callPyFunc("add", args, &var);
    qDebug() << __FUNCTION__ << "song" << var.toInt();

    QVariant var2 = 1.0;
    QVariantList args2 = {2.2, 3.3};
    rc = pyExcute.callPyFunc("add", args2, &var2);
    qDebug() << __FUNCTION__ << "song" << var2.toDouble();

    QVariant var3 = "";
    QVariantList args3 = {"hello ", "world"};
    rc = pyExcute.callPyFunc("add", args3, &var3);
    qDebug() << __FUNCTION__ << "song" << var3.toString();

    return a.exec();
}

运行

Qt调用python_第1张图片

 py文件位置在工程目录下

Qt调用python_第2张图片

py内容

# This Python file uses the following encoding: utf-8

# if __name__ == "__main__":
#     pass

def hello():
    print("Hello World")

def add(a, b):
    print("add test")
    return a+b

封装的类支持加载模块,即加载.py文件,并调用其中的函数,支持传参及返回值。传参和返回值使用Qt的通用类型QVariant进行实现。

不过感觉也不是很通用哈,没有实现传参传引用的效果,就是传参在函数内进行操作,函数外部的参数被修改,有需要的可以改造改造。传参和返回值目前只支持int、double、char *类型,根据需要再增加。

该方式调用链接的py库为release版本,对应的c++程序也需要是release

打包

最后是打包,由于python是解释性语言,正常来说需要有python的环境方能运行。一种打包方式是携带py的库一起打包,这样文件就会很大。目前网上常用的打包方式是pyinstaller,window下会打包成可执行文件exe,能够直接运行,打包出来也小了很多。试了一下纯py程序是能够用这种方式,c++调py用这种方式和c++程序一起打包没成功,根据调用原理来看,c++调用python是通过dll库调用.py文件中的函数,而pyinstall打包生成了平台可执行文件exe,c++没法调exe的内容,以我的理解来看只能带上py的库一起打包。

下面介绍携带py库一起打包的方式

由于我是Qt下调用,即打包Qt程序

1.先用windeployqt生成Qt程序需要的依赖,对熟悉Qt的来说这是基操了,这就不介绍了

2.在程序目录下创建py库文件夹,如python_39_64,把python安装目录下的DLLs和Lib复制到该文件夹下

Qt调用python_第3张图片

Qt调用python_第4张图片

3.把python的dll文件拷到程序目录下,把要执行的.py文件也拷到程序目录下,.pyc文件也可以

结构如下

Qt调用python_第5张图片

4.最后还有关键的一步,程序里需要指定py home,这样才能找到对应的py库

auto pyPath = QCoreApplication::applicationDirPath() + "/python_39_64"
Py_SetPythonHome(reinterpret_cast(pyPath.utf16()));

Py2可以是

char homePath[] = "python_27_32";
Py_SetPythonHome(homePath);

结语

以上是Qt /c++程序调用python的方式,缺点是打包后py的内容会暴露出来,使用.pyc文件应该能缓解一下这种情况,至少不是明文显示。当然更安全、更保密的做法可以是使用pyqt或者pyside2,使用python来写Qt程序,那所要调用的py代码也可以直接写在程序中了,最后打包出来是二进制程序,会安全很多。

你可能感兴趣的:(Qt)