Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does sys.excepthook behave differently when wrapped?

Tags:

python

pyqt

Qt silently catches exceptions in Python callbacks and exits the program with an error code. This can be demonstrated with a short example:

import sys
from PyQt5 import QtWidgets 

# _excepthook = sys.excepthook
# def exception_hook(exctype, value, traceback):
#     _excepthook(exctype, value, traceback)
# sys.excepthook = exception_hook

class Test(QtWidgets.QPushButton):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.setText("hello")
        self.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        print("clicked")
        raise Exception("wow")

app = QtWidgets.QApplication(sys.argv)
t = Test()
t.show()
app.exec_()

When clicking the button we get

clicked

Process finished with exit code 1

This answer (from which I modified the example) shows how to install a custom exception hook. So lets uncomment the code lines in the example above. Now it prints the traceback and does not exit the program every time we click the button.

The custom function is just a thin wrapper of the old function. Why does this cause different behavior when an exception is raised?

like image 836
MB-F Avatar asked Mar 02 '18 08:03

MB-F


1 Answers

In PyQt4 and old versions of PyQt5 (5.4 or older) the behaviour was to never exit the application in any of the situations you describe. This was changed in PyQt 5.5+ (to cause the application to exit) but only if there is no exception handler explicitly specified for sys.excepthook. This is somewhat mentioned in the documentation but also in more detail on the mailing list.

The relevant part from the documentation:

There are a number of situations where Python code is executed from C++. Python reimplementations of C++ virtual methods is probably the most common example. In previous versions, if the Python code raised an exception then PyQt would call Python’s PyErr_Print() function which would then call sys.excepthook(). The default exception hook would then display the exception and any traceback to stderr. There are number of disadvantages to this behaviour:

  • the application does not terminate, meaning the behaviour is different to when exceptions are raised in other situations
  • the output written to stderr may not be seen by the developer or user (particularly if it is a GUI application) thereby hiding the fact that the application is trying to report a potential bug.

This behaviour was deprecated in PyQt v5.4. In PyQt v5.5 an unhandled Python exception will result in a call to Qt’s qFatal() function. By default this will call abort() and the application will terminate. Note that an application installed exception hook will still take precedence.

The relavant part from the mailing list thread:

I have just discovered the change to PyQt 5.5 in which unhandled exceptions result in a call to qFatal(). Perhaps I am missing something important, but I am a confused about why this behavior was chosen. The documentation states that the problem with the old behavior is that "the application does not terminate, meaning the behaviour is different to when exceptions are raised in other situations". I have two concerns about this reasoning:

Because you can't cleanly exit Python when you're currently running C++ code.

  1. Unhandled exceptions in Python do not cause the program to terminate; they only cause sys.excepthook to be invoked.

Same with PyQt, if you set one.

It is perhaps also worth pointing out that the original question was raised on the pyqt mailing list by the creator of pyqtgraph and that staff at riverbank computing have said this new behaviour is not going away.

If you want to go to the source code, the relevant code is located in pyqt5/qpy/QtCore/qpycore_public_api.cpp (a forked version of PyQt5 is here)

like image 111
three_pineapples Avatar answered Oct 05 '22 23:10

three_pineapples