[PyQt] QDialog with connect lambda "leaks"

J Barchan jnbarchan at gmail.com
Thu Jul 19 15:06:16 BST 2018


On 19 July 2018 at 14:30, J Barchan <jnbarchan at gmail.com> wrote:

> ​​
> PyQt 5.7.
>
> I have a large body of existing UI code.  I have spent two days commenting
> in & out bits of code to try to discover why some of its QDialogs "leak"
> after calling QDialog.exec().
>
> My definition of "leak" here is: after executing from somewhere else
>
> dlg = QDialog(self)
> QDialog.exec()
>
> the instance of the dialog stays in existence permanently (as long as the
> caller exists, which for me is till end of program).  That means that every
> time that code gets executed, yet another new dialog is left around in
> memory, which adds up over time.  All I do to test is go into the dialog
> and immediately close it.
>
> I discover this by inspecting QtWidgets.QApplication.allWidgets() and
> reporting all QDialogs which are still in existence.  I see an
> ever-increasing number of these dialogs, one per each time it's constructed
> and executed, when & only when the code in the dialog is as follows.
>
> I have finally tracked down the problematic line in the dialog's
> __init__().  Some of them have:
>
> from elsewhere import ensureValidDecimal
> self.lineEdit = QLineEdit(self)
> self.lineEdit.editingFinished.connect(lambda: ensureValidDecimal(self))
>
> *The vital bit is: they connect() to a lambda which references self.*
>
> If the lambda does not need to pass self out as an argument, there will
> be no leak.
>
> If I go define (in this case) in the dialog (I actually sub-class from all
> my QDialogs so I can add stuff) a dedicated function to avoid the lambda:
>
>     def selfEnsureValidDecimal(self)
>         ensureValidDecimal(self)
>
>     self.lineEdit.editingFinished.connect(self.selfEnsureValidDecimal)
>
> then there will also be no leak.
>
> I can see that at some deep level there must be a reference counting issue
> here.  In some shape or form, the fact that we have a lambda which passes
> self to the outside world must mean Python/PyQt wants to keep a reference
> to the dialog and this must be preventing its destruction.
>
> But I don't know what to do about it.  There is a lot of code with a lot
> of dialogs with all sorts of code attached.  So I need some kind of
> explanation of what exactly can or cannot be done here, what to look for in
> code, etc.  Note that I do *not* wish to use QDialog.setAttribute(QtCore.Qt.WA_DeleteOnClose,
> True) on all my dialogs (I *believe* that would solve the leak, but it's
> not the point).  What must I *not* do if I do not expect such a
> self-reference to be left around preventing Python/PyQt from actually
> freeing up the dialog?
>
> --
> Kindest,
> Jonathan
>

​
Oh dear!  Googling hard, I have just come across
https://stackoverflow.com/a/48501804/489865, stating:

​Beware! As soon as you connect your signal to a lambda slot with a
> reference to self, your widget will not be garbage-collected! That's
> because lambda creates a closure with yet another uncollectable reference
> to the widget.



> Thus, self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p))
> is very evil :)


which confirms exactly what I have spent days discovering and typing in :(

So, that is indeed the problem?  And so I am required to go through all
code looking for exactly the pattern to change it?  Does this only apply to
QWidget.connect(), I only need to look at all connect() calls, or are there
other similar cases?




-- 
Kindest,
Jonathan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20180719/d2ecb741/attachment.html>


More information about the PyQt mailing list