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

Phil Thompson phil at riverbankcomputing.com
Wed Feb 10 15:42:53 GMT 2016


On 9 Feb 2016, at 4:37 am, Kovid Goyal <kovid at kovidgoyal.net> wrote:
> 
> Surely, the advice should be to keep a module level reference to the
> application global rather than to run code at module level. Like this:
> 
> app = None
> 
> def main():
>    global app
>    app = QApplication([])
>    app.exec_()
> 
> That way you get both behaviors. Although, in my experience, you cannot
> avoid segfaults on exit by relying on sip.setdestroyonexit().

It might be worth taking a step back on this...

Crashes are caused by C++ dtors being invoked (via the Python garbage collector) in an order that Qt is not happy with. The order is, in effect, random. Typically this happens at the end of a "scope", the most significant of which is when the interpreter exits.

Probably (but it is a guess) it would be best if the QApplication instance was destructed last of all.

PyQt5 disables the invocation of dtors when the interpreter is exiting. Therefore if the following pattern is used...

    if __name__ = '__main__':
        app = QApplication([])
        gui = QWidget()
        gui.show()
        app.exec()

...there shouldn't be a problem with crashes on exit. If anybody has an example where they think this is not the case then I'd like to know.

However it is not always possible to follow that pattern - setuptools requires a function entry point. When that function (ie. "scope") returns then the local objects can be garbage collected in a random order. Because the interpreter is still running, the dtors are still invoked.

PyQt5 tries to mitigate this to a certain extent. When a QApplication is garbage collected it first makes sure that any top-level widgets that still exist are owned by C++. This effectively disables the dtors of those widgets. However it does mean that widgets may still outlive the C++ QApplication instance - and maybe Qt doesn't like that.

The suggestion above (ie. the global reference to the QApplication object) has the effect of guaranteeing that the QApplication instance will outlive any objects that are garbage collected when main() returns. In fact the QApplication dtor will never be invoked as the object will only be garbage collected when the interpreter exits.

This pattern, therefore, should also avoid any crashes on exit. Again, if anybody has a counter example then I'd like to know.

I am considering changing the behaviour when the QApplication object gets garbage collected. Instead of transferring ownership of any top-level widgets it would instead explicitly invoke their dtors. This would guarantee the QApplication outlives any widgets (as the global reference trick does) but it would not be able to make the same guarantee regarding any other objects that might be referenced at the global level. However I think it would be an improvement over the current behaviour.

PyQt4 has a mechanism for tracking objects of certain classes (actually it is only done for QSystemTrayIcon) so that they get handled in a similar way to top-level widgets. I'd consider adding the same mechanism to PyQt5 if it turned out that crashes always involved certain classes. I'd also consider exposing that mechanism to applications to that they could add specific objects to a cleanup handler that is invoked just before the QApplication dtor.

As ever, feedback and comments welcome.

Phil


More information about the PyQt mailing list