[PyQt] QDialog with connect lambda "leaks"

Kovid Goyal kovid at kovidgoyal.net
Mon Jul 23 13:07:11 BST 2018


The way I work around this is to sue the following function to connect
signals to lambdas

def connect_lambda(bound_signal, self, func, **kw):
    r = weakref.ref(self)
    del self
    num_args = func.__code__.co_argcount - 1
    if num_args < 0:
        raise TypeError('lambda must take at least one argument')

    def slot(*args):
        ctx = r()
        if ctx is not None:
            if len(args) != num_args:
                args = args[:num_args]
            func(ctx, *args)

    bound_signal.connect(slot, **kw)


It can be used like this:

Instead of

self.editingFinished.connect(lambda: self.whatever())

do

connect_lambda(self.editingFinished, self, lambda self: self.whatever())


it would be nice if Phil added a connect_lambda or similar method to the
bound method class, so it could be used conveniently.

Kovid.

On Thu, Jul 19, 2018 at 02:30:30PM +0100, J Barchan 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

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


-- 
_____________________________________

Dr. Kovid Goyal 
https://www.kovidgoyal.net
https://calibre-ebook.com
_____________________________________


More information about the PyQt mailing list