connect()'s refcounting behavior seems buggy, and should be documented

Yuya Nishihara yuya at tcha.org
Sat Mar 19 10:40:12 GMT 2022


On Sat, 19 Mar 2022 10:04:49 +0000, Phil Thompson wrote:
> On 19/03/2022 02:28, Kovid Goyal wrote:
> > On Fri, Mar 18, 2022 at 05:26:13PM +0000, Phil Thompson wrote:  
> >> I've changed the PyQt6 behaviour to keep a reference to bound methods. 
> >> I'm
> >> not going to change PyQt5 as stability (avoiding unexpected 
> >> consequences) is
> >> more important than correctness so late in its lifecycle.  
> > 
> > Is the reference a weak or a strong reference? If its a strong 
> > reference
> > it will create reference cycles. Something as simple as:
> > 
> > class Widget(QDialog):
> > 
> >     def __init__(self):
> >         QDialog.__init__(self)
> >         self.bb = QDialogButtonBox(self)
> >         self.bb.accepted.connect(self.accept)
> > 
> >     def accept(self):
> >         ...
> > 
> > is now a reference cycle that the python garbage collector cannot
> > collect.  
> 
> The garbage collector should be able to detect and break such cycles. 
> Can you show a complete script based on the fragment above that 
> demonstrates a problem (I can't so far).

Hi, I thought about __del__, but apparently it's no longer problem on
Python 3.5+.

https://docs.python.org/3/library/gc.html#gc.garbage

One problem I can think of is that timer or large resources held by
the Widget won't be released until GC run.

class Widget(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.bb = QDialogButtonBox(self)
        self.bb.accepted.connect(lambda: self.accept()) # strong ref
        self.t = QTimer()
        self.t.timeout.connect(self.onTimeout)
        self.t.start(100)

    #def __del__(self):
    #    print('del')

    def accept(self):
        pass

    def onTimeout(self):
        print('run some heavy op on timeout')

def kick():
    w = Widget()
    w.exec()
    print('w should ideally be deleted here')

def run_gc():
    print('gc')
    gc.collect()

def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)
    QTimer.singleShot(0, kick)
    gcTimer = QTimer()
    gcTimer.timeout.connect(run_gc)
    gcTimer.start(5000)
    app.exec()


More information about the PyQt mailing list