[PyQt] Bug report: strange memory leak in overridden method paint in QItemDelegate

Roman Liverovskiy r.liverovskiy at gmail.com
Wed Jul 27 08:32:05 BST 2016


After updating to PyQt5 version 5.5.1 from PyQt5 version 5.4.1 I have
strange memory leaks, there are no any leaks on 5.4.1 version. I have
custom QAbstractTableModel and custom QTableView in my project with
delegates for data types. Here is example code, which has memory leaks in
lines 98 and 117 (it can be found using tracemalloc). Memory leak occur
when you select cells in table.
Also there is memory leak in PyQt5 version 5.6.

#!/usr/bin/python3
import tracemalloc

tracemalloc.start()

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

RW = (Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)

my_array = [['00','01','02'],
            ['10','11','12'],
            ['20','21','22']]

class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)

        tablemodel = MyTableModel(my_array, self)
        tableview = QTableView()
        tableview.setModel(tablemodel)

        self.cdelegates = []
        for i in range(3):
            d = RSpinDelegate(self)
            self.cdelegates.append(d)
            tableview.setItemDelegateForColumn(i, d)

        layout = QVBoxLayout(self)
        layout.addWidget(tableview)
        self.setLayout(layout)
        self.startTimer(5000)

    def timerEvent(self, e):
        snapshot = tracemalloc.take_snapshot()
        top_stats = snapshot.statistics('lineno')

        print("[ Top 10 ]")
        for stat in top_stats[:10]:
            print(stat)

class RSpinDelegate(QItemDelegate):
    def __init__(self, parent=None, decimals=0, step=1, range_=(0, 1e9),
edit=RW, suffix='', colorfill=None, stepFilter=None):
        super(RSpinDelegate, self).__init__(parent)
        self.decimals = decimals
        self.step = step
        self.range_ = range_
        self.edit = edit
        self.suffix = suffix
        self.colorfill = colorfill
        self.stepFilter = stepFilter

    def setDecimals(self, decimals):
        self.decimals = decimals

    def createEditor(self, parent, option, index):
        if self.edit == RW:
            if self.decimals:
                decimals = self.decimals
                dec = int(index.model().data(index,
RBaseTableModel.DECIMALS_ROLE))
                decimals = dec
                d = 10 ** (-decimals)
                editor = RDoubleSpinBox(parent)
                if self.stepFilter != None:
                    editor.installEventFilter(self.stepFilter)
                editor.setSingleStep(d)
                editor.setDecimals(decimals)
                editor.setRange(self.range_[0], self.range_[1])
                editor.setSuffix(self.suffix)
                self._editor = editor
                return editor
            else:
                editor = RSpinBox(parent)
                if self.stepFilter != None:
                    editor.installEventFilter(self.stepFilter)
                editor.setSingleStep(self.step)
                editor.setRange(self.range_[0], self.range_[1])
                editor.setSuffix(self.suffix)
                self._editor = editor
                return editor
            return None
        return None

    def setEditorData(self, editor, index):
        val = index.model().data(index, Qt.EditRole)
        try:
            editor.setValue(float(val.replace(' ', '')) if self.decimals !=
0 else int(val.replace(' ', '')))
        except:
            editor.setValue(editor.minimum())

    def setModelData(self, editor, model, index):
        model.setData(index, editor.value(), Qt.EditRole)

    def getBrush(self, option):
        brush = option.palette.base()
        if option.state & QStyle.State_Selected:# memory leak is here!
            if option.state & QStyle.State_Active:
                brush = option.palette.highlight()
            else:
                brush = option.palette.light()
        return brush

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

    def paint(self, painter, option, index):
        opt = QStyleOptionViewItem(option)
        if self.colorfill:
            brush = self.colorfill(index.model().data(index,
RBaseTableModel.INDEX_ROLE), option)
            if not(option.state & QStyle.State_Selected):
                painter.fillRect(option.rect, brush)
            opt.palette.setBrush(QPalette.Highlight, brush)
        else:
            brush = self.getBrush(option)
            painter.fillRect(option.rect, brush)# memory leak is here!
        super(RSpinDelegate, self).paint(painter, opt, index)

# création du modèle
class MyTableModel(QAbstractTableModel):
    refreshTable = pyqtSignal()

    def __init__(self, datain, parent = None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.arraydata = datain
        self.timer = self.startTimer(300)

    def timerEvent(self, e):
        if self.timer == e.timerId():
            self.refreshTable.emit()
        else:
            super(RBaseTableView, self).timerEvent(e)

    def refreshTableSlot(self):
        self.layoutAboutToBeChanged.emit()
        self.layoutChanged.emit()

    def rowCount(self, parent):
        return len(self.arraydata)

    def columnCount(self, parent):
        return len(self.arraydata[0])

    def data(self, index, role):
        if not index.isValid():
            return None
        elif role != Qt.DisplayRole:
            return None
        return (self.arraydata[index.row()][index.column()])

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

-- 
Faithfully yours, Roman I. Liverovskiy
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20160727/f66bd11e/attachment.html>


More information about the PyQt mailing list