Qt默默地捕获Python回调中的异常,并使用错误代码退出程序。这可以用一个简短的例子来证明:
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_()
点击我们得到的按钮
点击
进程以退出代码1结束
This answer(我修改了示例)显示了如何安装自定义异常挂钩。因此,请在上面的示例中取消注释代码行。现在它打印回溯,并且每次单击按钮时都不会退出程序。
自定义函数只是旧函数的一个薄包装器。为什么在引发异常时会导致不同的行为?
在PyQt4和旧版本的PyQt5(5.4或更早版本)中,行为是在您描述的任何情况下永远不会退出应用程序。这在PyQt 5.5+中已更改(导致应用程序退出),但前提是没有为sys.excepthook
明确指定的异常处理程序。这在documentation中有所提及,但在mailing list上也有更多细节。
文件中的相关部分:
在许多情况下,Python代码是从C ++执行的。 Python重新实现C ++虚拟方法可能是最常见的例子。在以前的版本中,如果Python代码引发异常,那么PyQt将调用Python的PyErr_Print()函数,然后调用sys.excepthook()。然后,默认的异常挂钩将显示异常以及对stderr的任何回溯。这种行为有许多缺点:
- 应用程序不会终止,这意味着行为与在其他情况下引发异常时的行为不同
- 开发人员或用户可能看不到写入stderr的输出(特别是如果它是GUI应用程序),从而隐藏了应用程序试图报告潜在错误的事实。
PyQt v5.4中不推荐使用此行为。在PyQt v5.5中,未处理的Python异常将导致调用Qt的qFatal()函数。默认情况下,这将调用abort(),应用程序将终止。请注意,应用程序安装的异常挂钩仍然优先。
邮件列表线程的相关部分:
我刚刚发现对PyQt 5.5的更改,其中未处理的异常导致调用qFatal()。也许我错过了一些重要的东西,但我对为什么选择这种行为感到困惑。该文档指出旧行为的问题是“应用程序不会终止,这意味着行为与在其他情况下引发异常时的行为不同”。我对这个推理有两个担忧:
因为当前运行C ++代码时无法干净地退出Python。
- Python中未处理的异常不会导致程序终止;它们只会导致调用sys.excepthook。
与PyQt相同,如果你设置一个。
也许值得指出的是,pyqtgraph的创建者在pyqt邮件列表上提出了原始问题,并且河岸计算的工作人员说这种新行为并没有消失。
如果你想转到源代码,相关代码位于pyqt5 / qpy / QtCore / qpycore_public_api.cpp(PyQt5的分叉版本是here)