QtWidget.grab() 在循环中调用时逐渐消耗内存
QtWidget.grab() consumes gradually memory when called in the loop
我有 PyQt5 GUI,我在其中加载了一些数据,然后将其绘制成图表
不要上传我创建的整个应用程序只是示例,其中使用什么崩溃...
一次,我需要将“GUI-visible”图形保存为图片(以备后用),所以调用:
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, 0, 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, 5, 740, 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, pen=pyqtgraph.mkPen('r', width=3, 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.Information)
str_to_show = "Once you see GUI, press CTRL+E and watch memory consumption in task manager"
msgBox.setText(str_to_show)
msgBox.setWindowTitle("Information")
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", None, 0, 350, 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.Information)
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("Information")
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
无关(至少,不是直接的),而是与QGraphicsViewcaching(pyqtgraph PlotWidgets实际上是QGraphicsView subclasses)。
事实上,如果您对整个抓取进行注释并改用 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 循环中设置每个 plot/grab。老实说,我不明白一次显示这么多图表有什么好处:350 个 QGraphicsViews 很多,我真诚地怀疑你真的需要用户同时访问它们一次,特别是考虑到使用 QTabWidget 会使它们难以访问。
还有:
- 您正在为每个选项卡创建一个
tab
QWidget,但您只是添加了一个图形视图,没有任何布局管理器;这会导致在调整主要 window 大小时出现问题(绘图小部件不调整其大小)并且无论如何都是不必要的:只需将绘图小部件添加到 QTabWidget:self.tabWidget.addTab(graph, str(tab_idx))
- 从不修改
pyuic
生成的文件,也不要试图模仿他们的行为;如果您从代码构建 UI,只需子 class 将充当 container/window 的小部件(在您的情况下为 QMainWindow),向其添加子小部件(使用中央小部件对于 main windows) 并实现 class 中的所有方法,否则请遵循关于 using Designer. 的官方指南
我有 PyQt5 GUI,我在其中加载了一些数据,然后将其绘制成图表
不要上传我创建的整个应用程序只是示例,其中使用什么崩溃...
一次,我需要将“GUI-visible”图形保存为图片(以备后用),所以调用:
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, 0, 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, 5, 740, 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, pen=pyqtgraph.mkPen('r', width=3, 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.Information)
str_to_show = "Once you see GUI, press CTRL+E and watch memory consumption in task manager"
msgBox.setText(str_to_show)
msgBox.setWindowTitle("Information")
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", None, 0, 350, 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.Information)
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("Information")
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
无关(至少,不是直接的),而是与QGraphicsViewcaching(pyqtgraph PlotWidgets实际上是QGraphicsView subclasses)。
事实上,如果您对整个抓取进行注释并改用 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 循环中设置每个 plot/grab。老实说,我不明白一次显示这么多图表有什么好处:350 个 QGraphicsViews 很多,我真诚地怀疑你真的需要用户同时访问它们一次,特别是考虑到使用 QTabWidget 会使它们难以访问。
还有:
- 您正在为每个选项卡创建一个
tab
QWidget,但您只是添加了一个图形视图,没有任何布局管理器;这会导致在调整主要 window 大小时出现问题(绘图小部件不调整其大小)并且无论如何都是不必要的:只需将绘图小部件添加到 QTabWidget:self.tabWidget.addTab(graph, str(tab_idx))
- 从不修改
pyuic
生成的文件,也不要试图模仿他们的行为;如果您从代码构建 UI,只需子 class 将充当 container/window 的小部件(在您的情况下为 QMainWindow),向其添加子小部件(使用中央小部件对于 main windows) 并实现 class 中的所有方法,否则请遵循关于 using Designer. 的官方指南