[PyQt] Realizing memory used by signals with objects

Phil Thompson phil at riverbankcomputing.com
Wed Apr 22 16:41:11 BST 2015


On 16/04/2015 10:59 am, Jan Kotanski wrote:
> Hi,
> 
> I've just tested memory leaks in my application and I've accounted
> a problem how to realize memory used by signals with objects, e.g.
> QSignalMapper.mapped signal with a QWidget parameter.
> After disconnecting this signal using garbage collector one can find
> that PyQt leaves a weakref in the memory.
> If I change the QWidget parameter to an int parameter this problem
> disappears.
> 
> I've tested this issue in PyQt 4.9.3 (wheezy), 4.11.2 (jessie),  5.3.2
> (jessie).
> 
> Is there any way to remove this weak reference (except catching it by 
> gc)?
> 
> Bellow I append my test script for PyQt4.
> 
> Thanks,
> Jan
> 
> 
> from PyQt4.QtGui import (QWidget, QPushButton, QDialog, QVBoxLayout)
> from PyQt4.QtCore import (QSignalMapper, pyqtSlot, QObject)
> 
> import sys
> import gc
> from collections import Counter
> 
> 
> class Con(QObject):
>     def __init__(self, parent=None):
>         super(Con, self).__init__(parent)
>         self.mapper = QSignalMapper(self)
>         self.mapper.mapped.connect(self.checked)
> 
>     @pyqtSlot(QWidget)
> #    @pyqtSlot(int)
>     def checked(self, widget):
>         print( "TYPE: %s" % type(widget))
> 
>     def close(self):
>         self.mapper.mapped.disconnect(self.checked)
>         self.mapper.setParent(None)
>         self.mapper = None
> 
> 
> class Dialog(QDialog):
>     def __init__(self, parent=None):
>         super(Dialog, self).__init__(parent)
>         self.button = QPushButton("Run")
>         vbl = QVBoxLayout()
>         vbl.addWidget(self.button)
>         self.con = None
>         self.setLayout(vbl)
>         self.button.clicked.connect(self.reset)
> 
>     def test(self):
>         self.con = Con(self)
>         self.con.close()
>         self.con.setParent(None)
>         del self.con
>         self.con = None
> 
>     @pyqtSlot()
>     def reset(self):
>         for i in range(1000):
>             self.test()
>             gc.collect()
>             print ("i = %s #OBJ = %s: %s" % (
>                 i, len(gc.get_objects()),
>                 Counter([type(g).__name__ for g in gc.get_objects()]
>                     ).most_common(4)))
> 
> 
> def main():
>     from PyQt4.QtGui import QApplication
>     app = QApplication(sys.argv)
>     form = Dialog()
>     form.show()
>     app.exec_()
> 
> if __name__ == "__main__":
>     main()

There is no leak in the above script - just things aren't being tidied 
up when you expect - the Qt event loop needs a chance to run in order to 
handle internal deleteLater() calls.

If you print the number of weak ref objects returned by get_objects() at 
the start of reset() you should see that this is the same each time.

BTW, if you want to avoid the weak refs in the first place then connect 
to the correct mapped() overload...

     self.mapper.mapped[QWidget].connect(self.checked)

Phil


More information about the PyQt mailing list