13

I'm trying to write an app that works in system tray using PyQt5. The code is sometimes raising exceptions, and I need to be able to catch them.

I would expect that when an exception occurs in an app, the main event loop is exited, so catching it like that should work:

try:
    application.exec()
except:
    do_stuff()

In the following example, when I press the "Raise" button, I only see the traceback, but I never see the error catched! printed.

from PyQt5 import QtWidgets, QtGui, QtCore

class ErrorApp():
    def __init__(self):
        # Init QApplication, QWidet and QMenu
        self.app = QtWidgets.QApplication([])
        self.widget = QtWidgets.QWidget()
        self.menu = QtWidgets.QMenu("menu", self.widget)

        # Add items to menu
        self.menu_action_raise = self.menu.addAction("Raise")
        self.menu_action_raise.triggered.connect(self.raise_error)

        self.menu_action_exit = self.menu.addAction("Exit")
        self.menu_action_exit.triggered.connect(self.app.exit)

        # Create the tray app
        self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon("logo.png"), self.widget)
        self.tray.setContextMenu(self.menu)

        # Show app
        self.tray.show()

    def raise_error(self):
        assert False

e = ErrorApp()

try:
    e.app.exec()

except:
    print("error catched!")

There are 2 similar questions, but the answers there don't do what I need to do:

Grab any exception in PyQt: the OP wants to monitor the exceptions, the even loop isn't exited Preventing PyQt to silence exceptions occurring in slots: the decorator answer simply doesn't work; adding sys.exit(1) to sys.excepthook just closes the whole program, without printing error catched!

eyllanesc
  • 235,170
  • 19
  • 170
  • 241

1 Answers1

28

You must use the exception and, if you want the event loop to end then you must call the quit()(or exit()) method.

import sys
import traceback
from PyQt5 import QtWidgets, QtGui, QtCore


class ErrorApp:
    # ...

    def raise_error(self):
        assert False


def excepthook(exc_type, exc_value, exc_tb):
    tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
    print("error catched!:")
    print("error message:\n", tb)
    QtWidgets.QApplication.quit()
    # or QtWidgets.QApplication.exit(0)


sys.excepthook = excepthook
e = ErrorApp()
ret = e.app.exec_()
print("event loop exited")
sys.exit(ret)

Output:

error catched!:
error message:
 Traceback (most recent call last):
  File "main.py", line 28, in raise_error
    assert False
AssertionError

event loop exited
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Oh, I see — I have to handle it inside the excepthook, instead of my attempt. Thanks :) – Mikołaj Kuranowski Apr 24 '19 at 14:59
  • should the normal `sys.__excepthook__(exc_type, exc_value, exc_tb)` function be call at the end of the exepthook function ? – ymmx Oct 14 '20 at 06:43
  • `exit` is a static method of `QCoreApplication` and `quit` is a "static slot" (not sure what the difference is) of `QCoreApplication`. In the "list of all members including inherited" for `QApplication` it includes `exit` (which obviously links to the `QCoreApplication` doc page), but doesn't include `quit`. And yet both seem to work as per your code. Are we just to assume these `static` methods/slots just "fall through" to the appropriate superclass? – mike rodent Aug 13 '21 at 14:37
  • 2
    @mikerodent `quit()` does appear in the QApplication member list, quit() is in the first column after primaryScreenChanged, it seems that the order is to put the properties, signals and slots first and then the other methods. Note: a slot is a function that is registered in the QMetaObject. – eyllanesc Aug 13 '21 at 17:00