天天看點

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 <Python.h>
#define slots Q_SLOTS
#include <QObject>
/**
* @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 <QVariant>
#include <QDebug>

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 <QCoreApplication>
#include <QDebug>
#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

 py檔案位置在工程目錄下

Qt調用python

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
Qt調用python

3.把python的dll檔案拷到程式目錄下,把要執行的.py檔案也拷到程式目錄下,.pyc檔案也可以

結構如下

Qt調用python

4.最後還有關鍵的一步,程式裡需要指定py home,這樣才能找到對應的py庫

auto pyPath = QCoreApplication::applicationDirPath() + "/python_39_64"
Py_SetPythonHome(reinterpret_cast<const wchar_t *>(pyPath.utf16()));
           

Py2可以是

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

結語

以上是Qt /c++程式調用python的方式,缺點是打包後py的内容會暴露出來,使用.pyc檔案應該能緩解一下這種情況,至少不是明文顯示。當然更安全、更保密的做法可以是使用pyqt或者pyside2,使用python來寫Qt程式,那所要調用的py代碼也可以直接寫在程式中了,最後打包出來是二進制程式,會安全很多。

Qt

繼續閱讀