[PyQt] Conflict between setuptools & requirements in official PyQt5 docs

Phil Thompson phil at riverbankcomputing.com
Wed Feb 10 22:38:05 GMT 2016


> On 10 Feb 2016, at 8:57 pm, Jones, Bryan <bjones at ece.msstate.edu> wrote:
> 
> Thanks for the feedback. The first two crashes (quickCrash and slowCrash) were, as you say, fairly obvious programmer errors. It would be nice if this would emit some sort of error ("QApplication destroyed; terminating application") with a backtrace.
> 
> The third case is more interesting -- the cause is more subtle and more likely (IMHO) to occur in "real" code. Here's a second pass at it, with a bit of cleanup. It looks like I've created a cycle between the QWidget (whose destroyed signal refers to foo) and the function foo (which retains a reference to the QWidget). When Python breaks the cycle during its garbage collection, it finalizes the QWidget, which invokes foo, which causes a crash. The QApplication instance, however, is still valid and has not been either finalized (that is, Python's __del__) or destroyed (the C++ destructor). Any thoughts?
> 
> import sys
> import os
> import platform
> import gc
> import sip
> from PyQt5.QtWidgets import QApplication, QWidget
> from PyQt5.QtCore import QT_VERSION_STR
> from PyQt5.Qt import PYQT_VERSION_STR
> 
> app = None
> 
> DEBUG = False
> 
> if DEBUG:
>     def printDestroyed(qObject):
>         qObjectStr = str(qObject)
>         def destroyed():
>             print('destroyed {}'.format(qObjectStr))
>         qObject.destroyed.connect(destroyed)
> 
>     class QApplicationF(QApplication):
>         def __del__(self):
>             print('finalized {}'.format(self))
> 
>     class QWidgetF(QWidget):
>         def __del__(self):
>             print('finalized {}'.format(self))
> 
>     def checkLeaks():
>         for o in gc.get_objects():
>             try:
>                 if not sip.isdeleted(o):
>                     sip.dump(o)
>             except TypeError:
>                 pass
> 
> else:
>     def printDestroyed(_): pass
>     QApplicationF = QApplication
>     QWidgetF = QWidget
>     def checkLeaks(): pass
> 
> def foo(x):
>     return lambda: x
> 
> def qtMain(doCrash):
>     global app
>     app = QApplicationF(sys.argv)
>     printDestroyed(app)
> 
>     w = QWidgetF()
>     printDestroyed(w)
> 
>     param = w if doCrash else 1
>     w.destroyed.connect(foo(param))
> 
> def gcCrash(createCycle, doCollect):
>     qtMain(createCycle)
>     checkLeaks()
>     gc.collect(doCollect)
> 
> if __name__ == '__main__':
>     # Both must be True for a crash.
>     gcCrash(True, True)

Attached is a patch (also in tonight's PyQt5 snapshot but you'll need a SIP snapshot as well) that will stop this particular crash.

Note that as soon as you start using __del__ the behaviour of the garbage collector changes.

Phil

-------------- next part --------------
A non-text attachment was scrubbed...
Name: pyqt5.patch
Type: application/octet-stream
Size: 1001 bytes
Desc: not available
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20160210/524010fb/attachment.obj>


More information about the PyQt mailing list