Exceptions in PyQt event loop and ipython

2020-04-10 01:36发布

I have a PyQt program that is displaying some widgets and buttons.

I want the program to run either as a standalone python instance, or inside an ipython environment. In this case, I use the following magic command in Jupyter console (formerly I had to use --gui=qt when launching the ipython qtconsole)

%pylab qt

In order to have a program that works both ways, my main module has the following lines:

APP = QtGui.Qapplication.instance() # retrieves the ipython qt application if any
if APP is None:
    APP = QtGui.QApplication(["foo"]) # create one if standalone execution

if __name__=='__main__':
    APP.exec_() # Launch the event loop here in standalone mode 

Here is my problem: exceptions generated by the event loop are very hard to detect by the user because they pop-out in the background console. I would like to catch any exception occuring in the event loop, and display a warning (for intance in the QMainWindow status bar to make the user aware that an exception occured).

I have tried several strategies, but there seems to be a conspiracy between PyQt's and Ipython's internal machineries to make this impossible:

  • Reimplement sys.excepthook (see Preventing PyQt to silence exceptions occurring in slots): not working because ipython keeps overwritting sys.excepthook
  • Detecting if IPython is running, and then using IPYTHON.set_custom_exc (Opening an IPython shell on any (uncatched) exception): Unforunately, qt event loop exceptions do not trigger the handler.
  • Overwrite QApplication.notify: bad luck, the native QApplication.notify function that I intend to call in the derived function is not throwing exceptions, nor does the return value (boolean) reflect the correct execution of the slots. The answer in this thread is interesting: How to log uncatched exceptions of a QApplication?, however, it seems that this strategy works in Qt c++, but the python wrapper of notify just prints the exceptions to the console instead of raising them.

It is a problem that keeps bothering me since a long time. Does anyone have a solution?

1条回答
ゆ 、 Hurt°
2楼-- · 2020-04-10 02:13

Actually, the dev's answer pointed me in the right direction: the problem is that each time an ipython cell is executed, a new sys.excepthook is monkeypatched, once the execution is done, the sys.excepthook is brought back to the previous one (see ipkernel/kernelapp.py).

Because of this, changing sys.excepthook in a normal ipython cell instruction will not change the excepthook that is executed during the qt event loop.

A simple solution is to monkeypatch sys.excepthook inside a qt event:

from PyQt4 import QtCore, QtGui
import sys
from traceback import format_exception

def new_except_hook(etype, evalue, tb):
    QtGui.QMessageBox.information(None, 
                                  str('error'),
                                  ''.join(format_exception(etype, evalue, tb)))

def patch_excepthook():
    sys.excepthook = new_except_hook
TIMER = QtCore.QTimer()
TIMER.setSingleShot(True)
TIMER.timeout.connect(patch_excepthook)
TIMER.start()

A nice thing about this method is that it works for standalone and ipython execution alike.

I guess one could also imagine to monkey patch a different version of new_except_hook depending on what widget is triggering the exception by calling patch_excepthook inside the event_handler of each widget.

查看更多
登录 后发表回答