Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are good practices for avoiding crashes / hangs in PyQt?

Tags:

python

pyqt

pyqt4

I love both python and Qt, but it's pretty obvious to me that Qt was not designed with python in mind. There are numerous ways to crash a PyQt / PySide application, many of which are extraordinarily difficult to debug, even with the proper tools.

I would like to know: what are good practices for avoiding crashes and lockups when using PyQt and PySide? These can be anything from general programming tips and support modules down to highly specific workarounds and bugs to avoid.

like image 353
Luke Avatar asked Aug 14 '12 02:08

Luke


People also ask

Is PyQt good for GUI?

PyQt is a Python binding for Qt, which is a set of C++ libraries and development tools that include platform-independent abstractions for Graphical User Interfaces (GUI), as well as networking, threads, regular expressions, SQL databases, SVG, OpenGL, XML, and many other powerful features.

Is PyQt better than PySide?

PyQt is significantly older than PySide and, partially due to that, has a larger community and is usually ahead when it comes to adopting new developments. It is mainly developed by Riverbank Computing Limited and distributed under GPL v3 and a commercial license.

Why use PySide instead of PyQt?

Advantages of PySide PySide represents the official set of Python bindings backed up by the Qt Company. PySide comes with a license under the LGPL, meaning it is simpler to incorporate into commercial projects when compared with PyQt. It allows the programmer to use QtQuick or QML to establish the user interface.

Is PyQt thread safe?

While some parts of the Qt framework are thread safe, much of it is not. The Qt C++ documentation provides a good overview of which classes are reentrant (can be used to instantiate objects in multiple threads).


2 Answers

General Programming Practices

  • If you must use multi-threaded code, never-ever access the GUI from a non-GUI thread. Always instead send a message to the GUI thread by emitting a signal or some other thread-safe mechanism.
  • Be careful with Model/View anything. TableView, TreeView, etc. They are difficult to program correctly, and any mistakes lead to untraceable crashing. Use Model Test to help ensure your model is internally consistent.
  • Understand the way Qt object management interacts with Python object management and the cases where this can go wrong. See http://python-camelot.s3.amazonaws.com/gpl/release/pyqt/doc/advanced/development.html
    • Qt objects with no parent are "owned" by Python; only Python may delete them.
    • Qt objects with a parent are "owned" by Qt and will be deleted by Qt if their parent is deleted.
    • Example: Core dump with PyQt4
  • A QObject should generally not have a reference to its parent or any of its ancestors (weak references are ok). This will cause memory leaks at best and occasional crashes as well.
  • Be aware of situations where Qt auto-deletes objects. If the python wrapper has not been informed that the C++ object was deleted, then accessing it will cause a crash. This can happen in many different ways due to the difficulty PyQt and PySide have in tracking Qt objects.

    • Compound widgets such as a QScrollArea and its scroll bars, QSpinBox and its QLineEdit, etc. (Pyside does not have this problem)
    • Deleting a QObject will automatically delete all of its children (however PyQt usually handles this correctly).
    • Removing items from QTreeWidget will cause any associated widgets (set with QTreeWidget.setItemWidget) to be deleted.

      # Example: from PyQt4 import QtGui, QtCore app = QtGui.QApplication([])  # Create a QScrollArea, get a reference to one of its scroll bars. w = QtGui.QWidget() sa = QtGui.QScrollArea(w) sb = sa.horizontalScrollBar()  # Later on, we delete the top-level widget because it was removed from the  # GUI and is no longer needed del w  # At this point, Qt has automatically deleted all three widgets. # PyQt knows that the QScrollArea is gone and will raise an exception if # you try to access it: sa.parent() Traceback (most recent call last):   File "<stdin>", line 1, in <module> RuntimeError: underlying C/C++ object has been deleted  # However, PyQt does not know that the scroll bar has also been deleted. # Since any attempt to access the deleted object will probably cause a  # crash, this object is 'toxic'; remove all references to it to avoid  # any accidents sb.parent() # Segmentation fault (core dumped) 

Specific Workarounds / Bugs

  • Changing the bounds of QGraphicsItems without calling prepareGeometryChange() first can cause crash.
  • Raising exceptions inside QGraphicsItem.paint() can cause crashes. Always catch exceptions inside paint() and display a message rather than letting the exception proceed uncaught.
  • QGraphicsItems should never keep a reference to the QGraphicsView they live in. (weakrefs are ok).
  • Using QTimer.singleShot repeatedly can cause lockups.
  • Avoid using QGraphicsView with QGLWidget.

Practices for Avoiding Exit Crashes

  • QGraphicsItems that are not part of a QGraphicsScene can cause crash on exit.
  • QObjects that reference their parent or any ancestor can cause an exit crash.
  • QGraphicsScene with no parent can cause an exit crash.
  • The easiest way to avoid exit crashes is to call os._exit() before python starts collecting Qt objects. However, this can be dangerous because some part of the program may be relying on proper exit handling to function correctly (for example, terminating log files or properly closing device handles). At a minimum, one should manually invoke the atexit callbacks before calling os._exit().
like image 58
7 revs, 2 users 86% Avatar answered Oct 14 '22 17:10

7 revs, 2 users 86%


Just adding to the point:

If you must use threads in your qt-based program, you really must disable the automatic garbage collector and do manual collections on the main thread (as described in http://pydev.blogspot.com.br/2014/03/should-python-garbage-collector-be.html) -- note that you should do that even if you make sure your objects don't have cycles (with a cycle you basically make your objects live until the python cyclic garbage collector bumps in, but sometimes if you have something as an exception it's possible that a frame is kept alive, so, in such a situation your object may still be kept alive longer than you anticipate)... in those cases, it's possible that the garbage collector bumps in in a secondary thread, which may cause qt to segfault (qt widgets must always be collected in the main thread).

like image 25
Fabio Zadrozny Avatar answered Oct 14 '22 16:10

Fabio Zadrozny