为什么 pyplot 与 PyQt4 的 QThread 有问题?

Why is pyplot having issues with PyQt4's QThread?

我正在设计一个可以创建多个待激活的 QThreads 的图形用户界面。每个线程使用 Pandas Excelwriter 创建一个 excel 工作簿,并使用 seaborn 创建一个热图并保存该热图(供用户以后使用),然后将其放入 excel 工作簿中。

我认为错误是 pyplot 没有成为创建的线程的实例..而是所有线程都指向的资源..如果我 运行 只有一个线程,那么没问题......两个或更多线程......在这个例子4中,有内部pyplot错误指向字典大小发生变化。

我遇到的问题是何时使用 pyplot。我是否必须像为 matplotlib 获得正确的后端那样做一些特定于 pyplot 的事情?我以为我对 matplotlib 所做的更改是 pyplot 固有的?

---main.py---

import sys
from MAIN_GUI import *
from PyQt4 import QtGui, QtCore
from excel_dummy import *


df1 = pd.DataFrame(np.array([[1,22222,33333],[2,44444,55555],[3,44444,22222],[4,55555,33333]]),columns=['hour','input','out'])
df2 = pd.DataFrame(np.array([[1,22233,33344],[2,44455,55566],[3,44455,22233],[4,55566,33344]]),columns=['hour','input','out'])
df3 = pd.DataFrame(np.array([[1,23456,34567],[2,98765,45674],[3,44444,22222],[4,44455,34443]]),columns=['hour','input','out'])
df4 = pd.DataFrame(np.array([[1,24442,33443],[2,44444,54455],[3,45544,24442],[4,54455,33443]]),columns=['hour','input','out'])

df_list = [df1,df2,df3,df4]

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)


class MAIN_GUI(QtGui.QMainWindow):
    def __init__(self):
        super(MAIN_GUI, self).__init__()
        self.uiM = Ui_MainWindow()
        self.uiM.setupUi(self)
        self.connect(self.uiM.updateALL_Button,QtCore.SIGNAL('clicked()'),self.newThread)

    def newThread(self):

        count = 0
        for df in df_list:
            count += 1
            Excelify = excelify(df,count)
            self.connect(Excelify,QtCore.SIGNAL('donethread(QString)'),(self.done))
            Excelify.start()


    def done(self):
        print('done')


main_gui = MAIN_GUI()
main_gui.show()
main_gui.raise_()
sys.exit(app.exec_())

---excel_dummy.py---

import pandas as pd

import numpy as np
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QThread
import time
import matplotlib as mpl
mpl.use('Agg')
from matplotlib.backends.backend_agg import FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import seaborn.matrix as sm

class excelify(QThread):
    def __init__(self,df,count):
        QThread.__init__(self)
        self.df = df
        self.count = count

    def run(self):

        heatit = self.heatmap()

        self.emit(QtCore.SIGNAL('donethread(QString)'),'')

    def heatmap(self):

        dfu = pd.DataFrame(self.df.groupby([self.df.input,self.df.hour]).size())
        dfu.reset_index(inplace=True)
        dfu.rename(columns={'0':'Count'})
        dfu.columns=['input','hour','Count']
        dfu_2 = dfu.copy()

        mask=0
        fig = Figure()
        ax = fig.add_subplot(1,1,1)
        fig.set_canvas(FigureCanvas(fig))
        df_heatmap = dfu_2.pivot('input','hour','Count').fillna(0)

        sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)

        plt.ylabel('ID')
        plt.xlabel('Hour')
        plt.title('heatmap for df' + str(self.count))
        plt.savefig(path + '/' + 'heat' + str(self.count) + '.png')
        plt.close()

---MAIN_GUI.py---

from PyQt4 import QtCore,QtGui
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.unicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(320,201)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.updateALL_Button = QtGui.QPushButton(self.centralwidget)
        self.updateALL_Button.setGeometry(QtCore.QRect(40,110,161,27))
        self.updateALL_Button.setFocusPolicy(QtCore.Qt.NoFocus)
        self.updateALL_Button.setObjectName(_fromUtf8("Options_updateALL_Button"))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 320, 24))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self,MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.updateALL_Button.setText(_translate("MainWindow", "updateALL", None))

虽然问题中的代码仍然不是真正的 minimal example(一些未定义的变量),但问题所在要清楚得多。

首先,一个问题可能是 MAIN_GUI class 丢失了对线程的引用,因此它会在完成之前被垃圾回收。可以通过将所有线程放在一个列表中来防止这种情况。 [参见下面的 MAIN_GUI 代码]

其次,不能直接使用pyplot同时对不同的图形进行操作。或者换句话说,如果同时存在多个plt.ylabel('ID')设置的ylabel,pyplot如何知道在哪个图中放置?
解决这个问题的方法是创建不同的图形,并且只使用 object oriented 方法在这些图形中工作。 [见下面的 excelify 代码]

这是代码的相关部分,我还将信号更改为 return 地块编号以便于调试。

MAIN_GUI:

class MAIN_GUI(QtGui.QMainWindow):
    def __init__(self):
        super(MAIN_GUI, self).__init__()
        self.uiM = Ui_MainWindow()
        self.uiM.setupUi(self)
        self.connect(self.uiM.updateALL_Button,QtCore.SIGNAL('clicked()'),self.newThread)
        self.threats=[]

    def newThread(self):
        count = 0
        for df in df_list:
            count += 1
            Excelify = excelify(df,count)
            self.connect(Excelify,QtCore.SIGNAL('donethread(int)'),(self.done))
            # appending all threats to a class attribute, 
            # such that they will persist and not garbage collected
            self.threats.append(Excelify)
            Excelify.start()

    def done(self, val=None):
        print('done with {nr}'.format(nr=val))

出类拔萃:

class excelify(QThread):
    def __init__(self,df,count):
        QThread.__init__(self)
        self.df = df
        self.count = count

    def run(self):
        heatit = self.heatmap()
        self.emit(QtCore.SIGNAL('donethread(int)'),self.count)

    def heatmap(self):
        print ("{nr} started".format(nr=self.count) )
        dfu = pd.DataFrame(self.df.groupby([self.df.input,self.df.hour]).size())
        dfu.reset_index(inplace=True)
        dfu.rename(columns={'0':'Count'})
        dfu.columns=['input','hour','Count']
        dfu_2 = dfu.copy()
        mask=0

        # create a figure and only work within this figure
        # no plt.something inside the threat
        fig = Figure()
        ax = fig.add_subplot(1,1,1)
        fig.set_canvas(FigureCanvas(fig))
        df_heatmap = dfu_2.pivot('input','hour','Count').fillna(0)

        sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)

        ax.set_ylabel('ID')
        ax.set_xlabel('Hour')
        ax.set_title('heatmap for df' + str(self.count))
        fig.savefig( 'heat' + str(self.count) + '.png')
        fig.clear()
        del fig