Qt+Python混合程式設計
專案使用Qt搭建了一個數據庫軟體,這個軟體還需要有一些資料分析、特徵重要度計算、效能預測等功能,而python的機器學習第三方庫比較成熟,使用起來也比較便捷,因此這裡需要用到Qt(c++)+python混合程式設計,在此記錄一下相關方法與問題,以方便自己與他人。
本專案使用的是QtCreator(Qt5.5.0)+VisualStudio2013+python3.6.5搭建。其他版本只要版本是正確對應的,都大同小異。
準備工作
假設你已經正確安裝了Qt和python,由於Qt中的slots
關鍵字與python重複,這裡我們需要修改一下檔案../Anaconda/include/object.h
(注意先將原檔案備份):
原檔案(448行):
typedef struct{
const char* name;
int basicsize;
int itemsize;
unsigned int flags;
PyType_Slot *slots; /* terminated by slot==0. */
} PyType_Spec;
修改為:
typedef struct{
const char* name;
int basicsize;
int itemsize;
unsigned int flags;
#undef slots // 這裡取消slots巨集定義
PyType_Slot *slots;/* terminated by slot==0. */
#define slots Q_SLOTS // 這裡恢復slots巨集定義與QT中QObjectDefs.h中一致
} PyType_Spec;
完成上述工作後我們需要在.pro
檔案中加入python的路徑資訊(我的python路徑是Y:/Anaconda
):
INCLUDEPATH += -I Y:/Anaconda/include
LIBS += -LY:/Anaconda/libs -lpython36
將python3.dll
,python36.dll
,pythoncom36.dll
pywintypes36.dll
放到.exe
目錄下。
Qt呼叫python指令碼
python檔案
建立一個python指令碼放在release
專案目錄下,這裡我們新建了一個kde.py
,其中包含無返回值函式plotKDE(x, column, kernel, algorithm, breadth_first, bw, leaf_size, atol, rtol, title)
用於繪製核KDE曲線與直方圖和有返回值函式loadData()
用於讀取本地.csv
檔案,影象繪製效果如下所示:
kde.py
部分程式碼如下(為方便表達,後續步驟中我們將plotKDE
函式簡寫為plotKDE(x, column, kernel)
):
import csv
import os
import matplotlib as mpl
from matplotlib import pyplot as plt
import numpy as np
from sklearn.neighbors import KernelDensity
mpl.use('TkAgg')
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
BASE_DIR = os.path.dirname(__file__)
file_path = os.path.join(BASE_DIR, 'train.csv')
def plotKDE(x, column, kernel='gaussian', algorithm='auto', breadth_first=1,
bw=30, leaf_size=40, atol=0, rtol=1E-8, title):
# kde
x_plot = np.linspace(min(x), max(x), 1000).reshape(-1, 1)
x = np.mat(x).reshape(-1, 1)
fig, ax = plt.subplots()
kde = KernelDensity(bandwidth=bw, algorithm=algorithm, kernel=kernel,
atol=atol, rtol=rtol, breadth_first=breadth_first,
leaf_size=leaf_size).fit(x)
log_dens = kde.score_samples(x_plot)
ax.hist(x, density=True, color='lightblue')
ax.plot(x_plot[:, 0], np.exp(log_dens))
plt.title(title[column])
plt.show()
def loadData():
x = []
with open(file_path, 'rt') as csvfile:
reader = csv.reader(csvfile)
for line in reader:
tmp = list(map(float, line[4:]))
x.append(tmp)
return x
Qvector轉pyObject型別
此處新建了一個類用於將Qt中儲存的資料的QVector<double>
型別等轉換為用於python指令碼的QObject
型別。QVector<QVector<double>>
的轉換方法可以此類推。
PyObject *Utility::UtilityFunction::convertLabelData(QVector<double> *labels)
{
int labelSize = labels->size();
PyObject *pArgs = PyList_New(labelSize);
for (int i = 0; i < labelSize; ++i) {
PyList_SetItem(pArgs, i, Py_BuildValue("d", (*labels)[i]));
}
return pArgs;
}
Qt呼叫python指令碼
在Qt專案中呼叫kde.py
的plotKDE
函式顯示影象(有輸入,無返回值):
// 初始化
Py_Initialize();
if (!Py_IsInitialized()) {
printf("inititalize failed");
qDebug() << "inititalize failed";
return ;
}
else {
qDebug() << "inititalize success";
}
// 載入模組,即loadtraindata.py
PyObject *pModule = PyImport_ImportModule("kde");
if (!pModule) {
PyErr_Print();
qDebug() << "not loaded!";
return ;
}
else {
qDebug() << "load module success";
}
// QVector<double> pKDE中存放了選中列的所有資料
PyObject *pKDEdata = Utility::UtilityFunction::convertLabelData(&pKDE); // 型別轉換
PyObject *pArg = PyTuple_New(3);
PyTuple_SetItem(pArg, 0, pKDEdata);
// int column表示選中的列的索引
PyTuple_SetItem(pArg, 1, Py_BuildValue("i", column));
// Qstring kernel表示核型別
PyTuple_SetItem(pArg, 2, Py_BuildValue("s", kernel.toStdString().c_str()));
// 載入函式loadData()
PyObject *pFunc = PyObject_GetAttrString(pModule, "plotKDE");
if (!pFunc) {
printf("get func failed!");
}
else {
qDebug() << "get func success";
}
PyObject_CallObject(pFunc, pArg);
Py_DECREF(pModule);
Py_DECREF(pFunc);
Py_Finalize();
在Qt專案中呼叫kde.py
的loadData
函式讀取本地資料(無輸入,有返回值):
Py_Initialize();
QVector<QVector<double> > *trainData; // 儲存python指令碼讀入的資料
if (!Py_IsInitialized()) {
printf("inititalize failed");
qDebug() << "inititalize failed";
return ;
}
else {
qDebug() << "inititalize success";
}
// 添加當前路徑(讀檔案的時候才需要)
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
// 載入模組,即loadtraindata.py
PyObject *pModule = PyImport_ImportModule("kde");
if (!pModule) {
PyErr_Print();
qDebug() << "not loaded!";
return ;
}
else {
qDebug() << "load module success";
}
// 載入函式loadData()
PyObject *pLoadFunc = PyObject_GetAttrString(pModule, "loadData");
if (!pLoadFunc) {
printf("get func failed!");
}
else {
qDebug() << "get func success";
}
PyObject *retObjectX = PyObject_CallObject(pLoadFunc, NULL); // 獲得python指令碼返回資料
if (retObjectX == NULL) {
qDebug() << "no return value";
return ;
}
/*
將retObjectX匯入trainData中(二維資料)
*/
int row = PyList_Size(retObjectX);
for (int i = 0; i < row; ++i) {
PyObject *lineObject = PyList_GetItem(retObjectX, i);
int col = PyList_Size(lineObject);
QVector<double> tmpVect;
for (int j = 0; j < col; ++j) {
PyObject *singleItem = PyList_GetItem(lineObject, j);
double item = PyFloat_AsDouble(singleItem);
tmpVect.push_back(item);
}
trainData->push_back(tmpVect);
}
Py_Finalize();
注意事項
這裡列寫一下軟體搭建過程中遇到的問題,以供參考。
- 重灌python的話別忘了修改
.pro
檔案中的python路徑; - importError:dll not load:
常見的matplotlib,numpy等DLL載入錯誤,通常是由python與對應的第三方包的版本不一致導致的。將anaconda檔案下的
python.dll
和python3.dll
檔案拷貝到qt可執行檔案exe同級目錄下並覆蓋。 - 多次呼叫
Py_Initialize()
和Py_Finalize()
可能會出現異常: 最好在main.cpp裡就輸入Py_Initialize()
,程式最後再Py_Finalize()
。
Reference:
本部落格與https://xuyunkun.com同步更新