在循环中调用QtWidget.grab逐渐消耗内存

问题描述

我有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将使它们难以访问。

也:

  1. 您要为每个选项卡创建一个tab QWidget,但您只是添加一个图形视图而没有任何布局管理器;这会在调整主窗口大小时导致问题(绘图小部件不会调整其大小),而且还是不必要的:只需将绘图小部件添加到QTabWidget:self.tabWidget.addTab(graph,str(tab_idx))
  2. 从不 修改pyuic生成的文件,也不要尝试模仿它们的行为;如果您是通过代码构建UI,则只需将将充当容器/窗口的小部件(用于QMainWindow)子类化,向其添加子小部件(对主窗口使用中央小部件)并实现该类中的所有方法,否则请遵循有关using Designer的官方指南。