为什么 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
我正在设计一个可以创建多个待激活的 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