问题描述
我有PyQt5 GUI,可以在其中加载一些数据,然后将这些数据绘制成图形
为了不上传整个应用程序,我只是创建了一个示例,说明崩溃的地方...
一次,我需要将“ GUI可见”图形另存为图片(以备后用),因此我调用:
grabbed = some_graphically_visible_widget.grab()
和
grabbed.save("My_name.png")
这两种方法在循环中最多调用350次,而在循环过程中 循环,python将抓取的“对象”保存在某处,因为作为memory_profiler 如图所示,我发现,每个.grab()周期的内存消耗都增加了约1.5MB
另外,我尝试了多种使用方式:
del grabbed
在循环结束时,或与之一起玩
gc.collect()
但是没有任何帮助,调用此循环始终会吃掉“它的一部分”。
下面是完整的示例应用程序,它可以一次完整运行 PyQt5和pyqtgraph模块被提供为“导入”:
import sys
import os
from random import randint
from PyQt5 import QtCore,QtWidgets,QtGui
from PyQt5.QtWidgets import QShortcut,QMessageBox
from PyQt5.QtGui import QKeySequence
import pyqtgraph
app = QtWidgets.QApplication(sys.argv)
class Ui_MainWindow(object):
def __init__(self):
self.graph_list = []
def setupUi(self,MainWindow):
MainWindow.setobjectName("Example")
MainWindow.resize(750,750)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setobjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget.setGeometry(QtCore.QRect(5,5,740,740))
self.tabWidget.setobjectName("tabWidget")
self.shortcut_CtrlL = QShortcut(QKeySequence('Ctrl+E'),self.centralwidget)
self.shortcut_CtrlL.activated.connect(self.doExport)
progress = QtWidgets.QProgressDialog("Creating enough graphs to simulate my case ... (350) ",None,350,self.centralwidget)
progress.setwindowTitle("...")
progress.show()
"Typical amount of graphs in application"
for tab_idx in range(350):
font = QtGui.QFont()
font.setPixelSize(15)
tab = QtWidgets.QWidget()
graph = pyqtgraph.PlotWidget(tab)
self.graph_list.append(graph)
graph.setGeometry(QtCore.QRect(5,740))
graph.addLegend(size=None,offset=(370,35))
x = []
y = []
min = []
max = []
for num in range(10):
x.append(num)
y.append(randint(0,10))
min.append(0)
max.append(10)
graph.plot(x,y,symbol='o',symbolPen='b',symbolBrush='b',name = "List of randomized values")
graph.plot(x,min,pen=pyqtgraph.mkPen('r',width=3,style=QtCore.Qt.DashLine))
graph.plot(x,max,style=QtCore.Qt.DashLine))
graph.showGrid(x=True)
graph.showGrid(y=True)
graph.setTitle(str(graph))
self.tabWidget.addTab(tab,str(tab_idx))
progress.setValue(tab_idx)
app.processEvents()
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.@R_200_4045@ion)
str_to_show = "Once you see GUI,press CTRL+E and watch memory consumption in task manager"
msgBox.setText(str_to_show)
msgBox.setwindowTitle("@R_200_4045@ion")
msgBox.setStandardButtons(QMessageBox.Ok)
msgBox.exec()
progress.close()
def doExport(self):
iterations = 0
progress = QtWidgets.QProgressDialog("Doing .grab() and .save() iterations \nNow you may watch increase RAM consumption - you must open taskmgr",self.centralwidget)
progress.setwindowTitle("...")
progress.show()
for graph in self.graph_list:
iterations += 1
grabbed = graph.grab()
grabbed.save("Dont_worry_I_will_be_multiple_times_rewritten.png")
progress.setValue(iterations)
app.processEvents()
progress.close()
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.@R_200_4045@ion)
str_to_show = str(iterations) + ' graphs was grabbed and converted into .png and \n python\'s RAM consumption had to increase ...'
msgBox.setText(str_to_show)
msgBox.setwindowTitle("@R_200_4045@ion")
msgBox.setStandardButtons(QMessageBox.Ok)
msgBox.exec()
if __name__ == "__main__":
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
解决方法
这与grab
(至少不是直接相关)无关,但与QGraphicsView caching(pyqtgraph PlotWidgets实际上是QGraphicsView子类)无关。
实际上,如果您对整个抓取进行注释并改用self.tabWidget.setCurrentIndex(iterations)
,则无论如何都会看到内存尖峰,这是因为grab()
显然会导致小部件被绘制并因此创建图形视图缓存。
您的问题的解决方案是为每个图形禁用缓存:
def setupUi(self,MainWindow):
# ...
for tab_idx in range(350):
# ...
graph = pyqtgraph.PlotWidget(tab)
self.graph_list.append(graph)
graph.setCacheMode(graph.CacheNone)
真正的问题是:您真的需要添加这么多图吗?如果只需要获取每个图形,则使用单个绘图小部件,并在for周期中设置每个绘图/抓图。老实说,我不明白一次显示这么多图有什么好处:350个QGraphicsViews 很多,我真诚地怀疑您是否真的需要用户一次访问所有图形,特别是考虑到使用QTabWidget将使它们难以访问。
也:
- 您要为每个选项卡创建一个
tab
QWidget,但您只是添加一个图形视图而没有任何布局管理器;这会在调整主窗口大小时导致问题(绘图小部件不会调整其大小),而且还是不必要的:只需将绘图小部件添加到QTabWidget:self.tabWidget.addTab(graph,str(tab_idx))
- 从不 修改
pyuic
生成的文件,也不要尝试模仿它们的行为;如果您是通过代码构建UI,则只需将将充当容器/窗口的小部件(用于QMainWindow)子类化,向其添加子小部件(对主窗口使用中央小部件)并实现该类中的所有方法,否则请遵循有关using Designer的官方指南。