[prev in list] [next in list] [prev in thread] [next in thread]
List: pykde
Subject: Re: [PyQt] QDialog with connect lambda "leaks"
From: J Barchan <jnbarchan () gmail ! com>
Date: 2018-07-24 7:30:50
Message-ID: CABz3M__0P8mjgmpi+h3i1reOOV+8F3hdt8V+9=TokZyQH-Y5yQ () mail ! gmail ! com
[Download RAW message or body]
[Attachment #2 (multipart/alternative)]
On 23 July 2018 at 13:07, Kovid Goyal <kovid@kovidgoyal.net> wrote:
> 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@riverbankcomputing.com
> > https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>
>
> --
> _____________________________________
>
> Dr. Kovid Goyal
> https://www.kovidgoyal.net
> https://calibre-ebook.com
> _____________________________________
> _______________________________________________
> PyQt mailing list PyQt@riverbankcomputing.com
> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
@Kovid Goyal
Blimey, that looks complicated, I'm a Python noob! Thank you for the
code. I'm not going to use it right now --- I prefer to rewrite code now
so as not to use self-referencing lambdas --- but have copied it to my
"utility functions" for the future :)
--
Kindest,
Jonathan
[Attachment #5 (text/html)]
<div dir="ltr"><div class="gmail_default" \
style="font-family:tahoma,sans-serif"><br></div><div class="gmail_extra"><br><div \
class="gmail_quote">On 23 July 2018 at 13:07, Kovid Goyal <span dir="ltr"><<a \
href="mailto:kovid@kovidgoyal.net" \
target="_blank">kovid@kovidgoyal.net</a>></span> wrote:<br><blockquote \
class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc \
solid;padding-left:1ex">The way I work around this is to sue the following function \
to connect<br> signals to lambdas<br>
<br>
def connect_lambda(bound_signal, self, func, **kw):<br>
r = weakref.ref(self)<br>
del self<br>
num_args = func.__code__.co_argcount - 1<br>
if num_args < 0:<br>
raise TypeError('lambda must take at least one argument')<br>
<br>
def slot(*args):<br>
ctx = r()<br>
if ctx is not None:<br>
if len(args) != num_args:<br>
args = args[:num_args]<br>
func(ctx, *args)<br>
<br>
bound_signal.connect(slot, **kw)<br>
<br>
<br>
It can be used like this:<br>
<br>
Instead of<br>
<br>
self.editingFinished.connect(<wbr>lambda: self.whatever())<br>
<br>
do<br>
<br>
connect_lambda(self.<wbr>editingFinished, self, lambda self: self.whatever())<br>
<br>
<br>
it would be nice if Phil added a connect_lambda or similar method to the<br>
bound method class, so it could be used conveniently.<br>
<br>
Kovid.<br>
<span class=""><br>
On Thu, Jul 19, 2018 at 02:30:30PM +0100, J Barchan wrote:<br>
> <br>
> PyQt 5.7.<br>
> <br>
> I have a large body of existing UI code. I have spent two days commenting<br>
> in & out bits of code to try to discover why some of its QDialogs \
"leak"<br> > after calling QDialog.exec().<br>
> <br>
> My definition of "leak" here is: after executing from somewhere \
else<br> > <br>
> dlg = QDialog(self)<br>
> QDialog.exec()<br>
> <br>
> the instance of the dialog stays in existence permanently (as long as the<br>
> caller exists, which for me is till end of program). That means that every<br>
> time that code gets executed, yet another new dialog is left around in<br>
> memory, which adds up over time. All I do to test is go into the dialog<br>
> and immediately close it.<br>
> <br>
> I discover this by inspecting QtWidgets.QApplication.<wbr>allWidgets() and<br>
> reporting all QDialogs which are still in existence. I see an<br>
> ever-increasing number of these dialogs, one per each time it's \
constructed<br> > and executed, when & only when the code in the dialog is as \
follows.<br> > <br>
> I have finally tracked down the problematic line in the dialog's \
__init__().<br> > Some of them have:<br>
> <br>
> from elsewhere import ensureValidDecimal<br>
> self.lineEdit = QLineEdit(self)<br>
> self.lineEdit.editingFinished.<wbr>connect(lambda: ensureValidDecimal(self))<br>
> <br>
</span>> *The vital bit is: they connect() to a lambda which references self.*<br>
<span class="">> <br>
> If the lambda does not need to pass self out as an argument, there will be<br>
> no leak.<br>
> <br>
> If I go define (in this case) in the dialog (I actually sub-class from all<br>
> my QDialogs so I can add stuff) a dedicated function to avoid the lambda:<br>
> <br>
> def selfEnsureValidDecimal(self)<br>
> ensureValidDecimal(self)<br>
> <br>
> self.lineEdit.editingFinished.<wbr>connect(self.<wbr>selfEnsureValidDecimal)<br>
> <br>
> then there will also be no leak.<br>
> <br>
> I can see that at some deep level there must be a reference counting issue<br>
> here. In some shape or form, the fact that we have a lambda which passes<br>
> self to the outside world must mean Python/PyQt wants to keep a reference<br>
> to the dialog and this must be preventing its destruction.<br>
> <br>
> But I don't know what to do about it. There is a lot of code with a lot \
of<br> > dialogs with all sorts of code attached. So I need some kind of<br>
> explanation of what exactly can or cannot be done here, what to look for in<br>
</span>> code, etc. Note that I do *not* wish to use<br>
> QDialog.setAttribute(QtCore.<wbr>Qt.WA_DeleteOnClose,<br>
> True) on all my dialogs (I *believe* that would solve the leak, but it's<br>
> not the point). What must I *not* do if I do not expect such a<br>
<span class="">> self-reference to be left around preventing Python/PyQt from \
actually<br> > freeing up the dialog?<br>
> <br>
> -- <br>
> Kindest,<br>
> Jonathan<br>
<br>
</span>> ______________________________<wbr>_________________<br>
> PyQt mailing list <a \
href="mailto:PyQt@riverbankcomputing.com">PyQt@riverbankcomputing.com</a><br> > <a \
href="https://www.riverbankcomputing.com/mailman/listinfo/pyqt" rel="noreferrer" \
target="_blank">https://www.<wbr>riverbankcomputing.com/<wbr>mailman/listinfo/pyqt</a><br>
<span class="HOEnZb"><font color="#888888"><br>
<br>
-- <br>
______________________________<wbr>_______<br>
<br>
Dr. Kovid Goyal <br>
<a href="https://www.kovidgoyal.net" rel="noreferrer" \
target="_blank">https://www.kovidgoyal.net</a><br> <a \
href="https://calibre-ebook.com" rel="noreferrer" \
target="_blank">https://calibre-ebook.com</a><br> \
______________________________<wbr>_______<br> \
______________________________<wbr>_________________<br> PyQt mailing list <a \
href="mailto:PyQt@riverbankcomputing.com">PyQt@riverbankcomputing.com</a><br> <a \
href="https://www.riverbankcomputing.com/mailman/listinfo/pyqt" rel="noreferrer" \
target="_blank">https://www.<wbr>riverbankcomputing.com/<wbr>mailman/listinfo/pyqt</a></font></span></blockquote></div><br><div \
style="font-family:tahoma,sans-serif" class="gmail_default">@Kovid Goyal</div><div \
style="font-family:tahoma,sans-serif" class="gmail_default">Blimey, that looks \
complicated, I'm a Python noob! Thank you for the code. I'm not going to \
use it right now --- I prefer to rewrite code now so as not to use self-referencing \
lambdas --- but have copied it to my "utility functions" for the future \
:)</div><br clear="all"><br>-- <br><div class="gmail_signature" \
data-smartmail="gmail_signature"><div dir="ltr"><div><div dir="ltr"><div><span \
style="font-family:tahoma,sans-serif">Kindest,</span></div><div><span \
style="font-family:tahoma,sans-serif">Jonathan</span></div></div></div></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