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();
}
運作

py檔案位置在工程目錄下
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複制到該檔案夾下
3.把python的dll檔案拷到程式目錄下,把要執行的.py檔案也拷到程式目錄下,.pyc檔案也可以
結構如下
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代碼也可以直接寫在程式中了,最後打包出來是二進制程式,會安全很多。