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