[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pykde
Subject:    [PyQt] Bug report: strange memory leak in overridden method paint in QItemDelegate
From:       Roman Liverovskiy <r.liverovskiy () gmail ! com>
Date:       2016-07-27 7:32:05
Message-ID: CAN9jV38xkfKEetPghRbo1umx-SXfy1+rHcdL8v8ikJAwiAjUjg () mail ! gmail ! com
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


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

[Attachment #5 (text/html)]

<div dir="ltr"><div>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.</div><div>Also there is memory leak in PyQt5 version \
5.6.</div><div><br></div><div>#!/usr/bin/python3  </div><div>import tracemalloc  \
</div><div><br></div><div>tracemalloc.start()  </div><div><br></div><div>import sys  \
</div><div>from PyQt5.QtCore import *  </div><div>from PyQt5.QtGui import *  \
</div><div>from PyQt5.QtWidgets import *  </div><div><br></div><div>RW = \
(Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)  \
</div><div><br></div><div>my_array = [[&#39;00&#39;,&#39;01&#39;,&#39;02&#39;],  \
</div><div>                  [&#39;10&#39;,&#39;11&#39;,&#39;12&#39;],  </div><div>   \
[&#39;20&#39;,&#39;21&#39;,&#39;22&#39;]]  </div><div><br></div><div>class \
MyWindow(QWidget):  </div><div>      def __init__(self, *args):  </div><div>          \
QWidget.__init__(self, *args)  </div><div><br></div><div>            tablemodel = \
MyTableModel(my_array, self)  </div><div>            tableview = QTableView()  \
</div><div>            tableview.setModel(tablemodel)  </div><div><br></div><div>     \
self.cdelegates = []  </div><div>            for i in range(3):  </div><div>          \
d = RSpinDelegate(self)  </div><div>                  self.cdelegates.append(d)  \
</div><div>                  tableview.setItemDelegateForColumn(i, d)  \
</div><div><br></div><div>            layout = QVBoxLayout(self)  </div><div>         \
layout.addWidget(tableview)  </div><div>            self.setLayout(layout)  \
</div><div>            self.startTimer(5000)  </div><div><br></div><div>      def \
timerEvent(self, e):  </div><div>            snapshot = tracemalloc.take_snapshot()  \
</div><div>            top_stats = snapshot.statistics(&#39;lineno&#39;)  \
</div><div><br></div><div>            print(&quot;[ Top 10 ]&quot;)  </div><div>      \
for stat in top_stats[:10]:  </div><div>                  print(stat)  \
</div><div><br></div><div>class RSpinDelegate(QItemDelegate):  </div><div>      def \
__init__(self, parent=None, decimals=0, step=1, range_=(0, 1e9), edit=RW, \
suffix=&#39;&#39;, colorfill=None, stepFilter=None):  </div><div>            \
super(RSpinDelegate, self).__init__(parent)  </div><div>            self.decimals = \
decimals  </div><div>            self.step = step  </div><div>            self.range_ \
= range_  </div><div>            self.edit = edit  </div><div>            self.suffix \
= suffix  </div><div>            self.colorfill = colorfill  </div><div>            \
self.stepFilter = stepFilter  </div><div><br></div><div>      def setDecimals(self, \
decimals):  </div><div>            self.decimals = decimals  \
</div><div><br></div><div>      def createEditor(self, parent, option, index):  \
</div><div>            if self.edit == RW:  </div><div>                  if \
self.decimals:  </div><div>                        decimals = self.decimals  \
</div><div>                        dec = int(index.model().data(index, \
RBaseTableModel.DECIMALS_ROLE))  </div><div>                        decimals = dec  \
</div><div>                        d = 10 ** (-decimals)  </div><div>                 \
editor = RDoubleSpinBox(parent)  </div><div>                        if \
self.stepFilter != None:  </div><div>                              \
editor.installEventFilter(self.stepFilter)  </div><div>                        \
editor.setSingleStep(d)  </div><div>                        \
editor.setDecimals(decimals)  </div><div>                        \
editor.setRange(self.range_[0], self.range_[1])  </div><div>                        \
editor.setSuffix(self.suffix)  </div><div>                        self._editor = \
editor  </div><div>                        return editor  </div><div>                 \
else:  </div><div>                        editor = RSpinBox(parent)  </div><div>      \
if self.stepFilter != None:  </div><div>                              \
editor.installEventFilter(self.stepFilter)  </div><div>                        \
editor.setSingleStep(self.step)  </div><div>                        \
editor.setRange(self.range_[0], self.range_[1])  </div><div>                        \
editor.setSuffix(self.suffix)  </div><div>                        self._editor = \
editor  </div><div>                        return editor  </div><div>                 \
return None  </div><div>            return None  </div><div><br></div><div>      def \
setEditorData(self, editor, index):  </div><div>            val = \
index.model().data(index, Qt.EditRole)  </div><div>            try:  </div><div>      \
editor.setValue(float(val.replace(&#39; &#39;, &#39;&#39;)) if self.decimals != 0 \
else int(val.replace(&#39; &#39;, &#39;&#39;)))</div><div>            except:  \
</div><div>                  editor.setValue(editor.minimum())  \
</div><div><br></div><div>      def setModelData(self, editor, model, index):  \
</div><div>            model.setData(index, editor.value(), Qt.EditRole)  \
</div><div><br></div><div>      def getBrush(self, option):  </div><div>            \
brush = option.palette.base()  </div><div>            if option.state &amp; \
QStyle.State_Selected:# memory leak is here!</div><div>                  if \
option.state &amp; QStyle.State_Active:  </div><div>                        brush = \
option.palette.highlight()  </div><div>                  else:  </div><div>           \
brush = option.palette.light()  </div><div>            return brush  \
</div><div><br></div><div>      def updateEditorGeometry(self, editor, option, \
index):  </div><div>            editor.setGeometry(option.rect)  \
</div><div><br></div><div>      def paint(self, painter, option, index):  </div><div> \
opt = QStyleOptionViewItem(option)  </div><div>            if self.colorfill:  \
</div><div>                  brush = self.colorfill(index.model().data(index, \
RBaseTableModel.INDEX_ROLE), option)  </div><div>                  if \
not(option.state &amp; QStyle.State_Selected):  </div><div>                        \
painter.fillRect(option.rect, brush)  </div><div>                  \
opt.palette.setBrush(QPalette.Highlight, brush)  </div><div>            else:  \
</div><div>                  brush = self.getBrush(option)  </div><div>               \
painter.fillRect(option.rect, brush)# memory leak is here!</div><div>            \
super(RSpinDelegate, self).paint(painter, opt, index)  </div><div><br></div><div># \
création du modèle  </div><div>class MyTableModel(QAbstractTableModel):  \
</div><div>      refreshTable = pyqtSignal()  </div><div><br></div><div>      def \
__init__(self, datain, parent = None, *args):  </div><div>            \
QAbstractTableModel.__init__(self, parent, *args)  </div><div>            \
self.arraydata = datain  </div><div>            self.timer = self.startTimer(300)  \
</div><div><br></div><div>      def timerEvent(self, e):  </div><div>            if \
self.timer == e.timerId():  </div><div>                  self.refreshTable.emit()  \
</div><div>            else:  </div><div>                  super(RBaseTableView, \
self).timerEvent(e)  </div><div><br></div><div>      def refreshTableSlot(self):  \
</div><div>            self.layoutAboutToBeChanged.emit()  </div><div>            \
self.layoutChanged.emit()  </div><div><br></div><div>      def rowCount(self, \
parent):  </div><div>            return len(self.arraydata)  \
</div><div><br></div><div>      def columnCount(self, parent):  </div><div>           \
return len(self.arraydata[0])  </div><div><br></div><div>      def data(self, index, \
role):  </div><div>            if not index.isValid():  </div><div>                  \
return None  </div><div>            elif role != Qt.DisplayRole:  </div><div>         \
return None  </div><div>            return \
(self.arraydata[index.row()][index.column()])  </div><div><br></div><div>if __name__ \
== &quot;__main__&quot;:  </div><div>      app = QApplication(sys.argv)  </div><div>  \
w = MyWindow()  </div><div>      w.show()  </div><div>      \
sys.exit(app.exec_())</div><div><br></div>-- <br><div class="gmail_signature"><div \
dir="ltr"><div>Faithfully yours, Roman I. Liverovskiy</div></div></div> </div>


[Attachment #6 (text/plain)]

_______________________________________________
PyQt mailing list    PyQt@riverbankcomputing.com
https://www.riverbankcomputing.com/mailman/listinfo/pyqt

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic