python - Exception handled surprisingly in Pyside slots -
problem: when exceptions raised in slots, invoked signals, not seem propagate usual through pythons call stack. in example code below invoking:
on_raise_without_signal()
: handle exception expected.on_raise_with_signal()
: print exception , unexpectedly print success messageelse
block.
question: reason behind exception being handled surprisingly when raised in slot? implementation detail/limitation of pyside qt wrapping of signals/slots? there read in docs?
ps: came across topic when got surprising results upon using try/except/else/finally when implementing qabstracttablemodels
virtual methods insertrows()
, removerows()
.
# -*- coding: utf-8 -*- """testing exception handling in pyside slots.""" __future__ import unicode_literals, print_function, division import logging import sys pyside import qtcore pyside import qtgui logging.basicconfig(level=logging.debug) logger = logging.getlogger(__name__) class exceptiontestwidget(qtgui.qwidget): raise_exception = qtcore.signal() def __init__(self, *args, **kwargs): super(exceptiontestwidget, self).__init__(*args, **kwargs) self.raise_exception.connect(self.slot_raise_exception) layout = qtgui.qvboxlayout() self.setlayout(layout) # button invoke handler handles raised exception expected btn_raise_without_signal = qtgui.qpushbutton("raise without signal") btn_raise_without_signal.clicked.connect(self.on_raise_without_signal) layout.addwidget(btn_raise_without_signal) # button invoke handler handles raised exception via signal unexpectedly btn_raise_with_signal = qtgui.qpushbutton("raise signal") btn_raise_with_signal.clicked.connect(self.on_raise_with_signal) layout.addwidget(btn_raise_with_signal) def slot_raise_exception(self): raise valueerror("valueerror on purpose") def on_raise_without_signal(self): """call function raises exception directly.""" try: self.slot_raise_exception() except valueerror exception_instance: logger.error("{}".format(exception_instance)) else: logger.info("on_raise_without_signal() executed successfully") def on_raise_with_signal(self): """call slot raises exception via signal.""" try: self.raise_exception.emit() except valueerror exception_instance: logger.error("{}".format(exception_instance)) else: logger.info("on_raise_with_signal() executed successfully") if (__name__ == "__main__"): application = qtgui.qapplication(sys.argv) widget = exceptiontestwidget() widget.show() sys.exit(application.exec_())
as you've noted in question, real issue here treatment of unhandled exceptions raised in python code executed c++. not signals: affects reimplemented virtual methods well.
in pyside, pyqt4, , pyqt5 versions 5.5, default behaviour automatically catch error on c++ side , dump traceback stderr. normally, python script automatically terminate after this. not happens here. instead, pyside/pyqt script carries on regardless, , many people quite rightly regard bug (or @ least misfeature). in pyqt-5.5, behaviour has been changed qfatal()
called on c++ side, , program abort normal python script would. (i don't know current situation pyside2, though).
so - should done this? best solution versions of pyside , pyqt install exception hook - because take precedence on default behaviour (whatever may be). unhandled exception raised signal, virtual method or other python code firstly invoke sys.excepthook
, allowing customise behaviour in whatever way like.
in example script, mean adding this:
def excepthook(cls, exception, traceback): print('calling excepthook...') logger.error("{}".format(exception)) sys.excepthook = excepthook
and exception raised on_raise_with_signal
can handled in same way other unhandled exceptions.
of course, imply best practice pyside/pyqt applications use largely centralised exception handling. includes showing kind of crash-dialog user can report unexpected errors.
Comments
Post a Comment