为什么sys.excepthook在包装时表现不同?

时间:2022-03-04 23:00:56

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

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_()

When clicking the button we get

点击我们得到的按钮

clicked

Process finished with exit code 1

进程以退出代码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?

自定义函数只是旧函数的一个薄包装器。为什么在引发异常时会导致不同的行为?

1 个解决方案

#1


3  

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.

在PyQt4和旧版本的PyQt5(5.4或更早版本)中,行为是在您描述的任何情况下永远不会退出应用程序。这在PyQt 5.5+中已更改(导致应用程序退出),但前提是没有为sys.excepthook显式指定的异常处理程序。这在文档中有所提及,但在邮件列表中也有更详细的说明。

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:

在许多情况下,Python代码是从C ++执行的。 Python重新实现C ++虚拟方法可能是最常见的例子。在以前的版本中,如果Python代码引发异常,那么PyQt将调用Python的PyErr_Print()函数,然后调用sys.excepthook()。然后,默认的异常挂钩将显示异常以及对stderr的任何回溯。这种行为有许多缺点:

  • 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.
  • 开发人员或用户可能看不到写入stderr的输出(特别是如果它是GUI应用程序),从而隐藏了应用程序试图报告潜在错误的事实。

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.

PyQt v5.4中不推荐使用此行为。在PyQt v5.5中,未处理的Python异常将导致调用Qt的qFatal()函数。默认情况下,这将调用abort(),应用程序将终止。请注意,应用程序安装的异常挂钩仍然优先。

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:

我刚刚发现对PyQt 5.5的更改,其中未处理的异常导致调用qFatal()。也许我错过了一些重要的东西,但我对为什么选择这种行为感到困惑。该文档指出旧行为的问题是“应用程序不会终止,这意味着行为与在其他情况下引发异常时的行为不同”。我对这个推理有两个担忧:

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

因为当前运行C ++代码时无法干净地退出Python。

  1. Unhandled exceptions in Python do not cause the program to terminate; they only cause sys.excepthook to be invoked.
  2. Python中未处理的异常不会导致程序终止;它们只会导致调用sys.excepthook。

Same with PyQt, if you set one.

与PyQt相同,如果你设置一个。

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.

也许值得指出的是,pyqtgraph的创建者在pyqt邮件列表上提出了原始问题,并且河岸计算的工作人员说这种新行为并没有消失。

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)

如果你想转到源代码,相关代码位于pyqt5 / qpy / QtCore / qpycore_public_api.cpp(PyQt5的分叉版本在这里)

#1


3  

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.

在PyQt4和旧版本的PyQt5(5.4或更早版本)中,行为是在您描述的任何情况下永远不会退出应用程序。这在PyQt 5.5+中已更改(导致应用程序退出),但前提是没有为sys.excepthook显式指定的异常处理程序。这在文档中有所提及,但在邮件列表中也有更详细的说明。

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:

在许多情况下,Python代码是从C ++执行的。 Python重新实现C ++虚拟方法可能是最常见的例子。在以前的版本中,如果Python代码引发异常,那么PyQt将调用Python的PyErr_Print()函数,然后调用sys.excepthook()。然后,默认的异常挂钩将显示异常以及对stderr的任何回溯。这种行为有许多缺点:

  • 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.
  • 开发人员或用户可能看不到写入stderr的输出(特别是如果它是GUI应用程序),从而隐藏了应用程序试图报告潜在错误的事实。

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.

PyQt v5.4中不推荐使用此行为。在PyQt v5.5中,未处理的Python异常将导致调用Qt的qFatal()函数。默认情况下,这将调用abort(),应用程序将终止。请注意,应用程序安装的异常挂钩仍然优先。

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:

我刚刚发现对PyQt 5.5的更改,其中未处理的异常导致调用qFatal()。也许我错过了一些重要的东西,但我对为什么选择这种行为感到困惑。该文档指出旧行为的问题是“应用程序不会终止,这意味着行为与在其他情况下引发异常时的行为不同”。我对这个推理有两个担忧:

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

因为当前运行C ++代码时无法干净地退出Python。

  1. Unhandled exceptions in Python do not cause the program to terminate; they only cause sys.excepthook to be invoked.
  2. Python中未处理的异常不会导致程序终止;它们只会导致调用sys.excepthook。

Same with PyQt, if you set one.

与PyQt相同,如果你设置一个。

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.

也许值得指出的是,pyqtgraph的创建者在pyqt邮件列表上提出了原始问题,并且河岸计算的工作人员说这种新行为并没有消失。

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)

如果你想转到源代码,相关代码位于pyqt5 / qpy / QtCore / qpycore_public_api.cpp(PyQt5的分叉版本在这里)