python - 在 Pyside 插槽中意外处理异常

标签 python exception pyqt pyside signals-slots

问题:当槽中出现异常时,由信号调用,它们似乎不会像往常一样通过 Python 调用堆栈传播。在下面调用的示例代码中:

  • on_raise_without_signal():将按预期处理异常。
  • on_raise_with_signal():将打印异常,然后意外地打印来自 else block 的成功消息。

问题在插槽中引发异常时意外处理的原因是什么?它是 PySide Qt 信号/插槽包装的一些实现细节/限制吗?文档中有什么值得阅读的内容吗?

PS: 我最初遇到这个话题是因为我在实现 QAbstractTableModels 时使用 try/except/else/finally 得到了惊人的结果虚拟方法 insertRows()removeRows()


# -*- coding: utf-8 -*-
"""Testing exception handling in PySide slots."""
from __future__ import unicode_literals, print_function, division

import logging
import sys

from PySide import QtCore
from 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 to invoke handler that handles raised exception as 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 to invoke handler that handles raised exception via signal unexpectedly
        btn_raise_with_signal = QtGui.QPushButton("Raise with 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 that raises exception directly."""
        try:
            self.slot_raise_exception()
        except ValueError as exception_instance:
            logger.error("{}".format(exception_instance))
        else:
            logger.info("on_raise_without_signal() executed successfully")

    def on_raise_with_signal(self):
        """Call slot that raises exception via signal."""
        try:
            self.raise_exception.emit()
        except ValueError as 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_())

最佳答案

正如您在问题中已经指出的那样,这里真正的问题是如何处理从 C++ 执行的 python 代码中引发的未处理异常。所以这不仅与信号有关:它还会影响重新实现的虚拟方法。

在 PySide、PyQt4 和所有 5.5 之前的 PyQt5 版本中,默认行为是自动捕获 C++ 端的错误并将回溯转储到 stderr。通常,python 脚本也会在此之后自动终止。但这不是这里发生的事情。相反,PySide/PyQt 脚本不顾一切地继续运行,许多人非常正确地将其视为错误(或至少是错误功能)。在 PyQt-5.5 中,此行为现已更改,因此 qFatal() 也会在 C++ 端调用,程序将像普通 python 脚本一样中止。 (不过我不知道 PySide2 目前的情况如何)。

那么 - 对于这一切应该怎么办?所有版本的 PySide 和 PyQt 的最佳解决方案是安装 exception hook - 因为它总是优先于默认行为(无论是什么)。信号、虚方法或其他 Python 代码引发的任何未处理的异常将首先调用 sys.excepthook,允许您以任何您喜欢的方式完全自定义行为。

在您的示例脚本中,这可能只是意味着添加如下内容:

def excepthook(cls, exception, traceback):
    print('calling excepthook...')
    logger.error("{}".format(exception))

sys.excepthook = excepthook

现在可以像处理所有其他未处理的异常一样处理 on_raise_with_signal 引发的异常。

当然,这确实意味着大多数 PySide/PyQt 应用程序的最佳实践是使用主要集中的异常处理。这通常包括显示某种崩溃对话框,用户可以在其中报告意外错误。

关于python - 在 Pyside 插槽中意外处理异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45787237/

相关文章:

python - 如何在QLineEdit中显示QListwidget选中的项目?

python - 按字母顺序排序 Django models.py : refering to a model defined below

python - 使用 SageMaker Lifecycle 配置在启动时执行 jupyter notebook

python - 无需明确定义要抓取的每个字段即可抓取数据

python - 将 F2Py 与 OpenACC 一起使用会在 Python 中出现导入错误

java - 计算排序列表中单词的频率

python - 如何在帧中生成动画

java websocket EOFException发生

java - 如何获取 JSON 数据的多个可选值?

javascript - 抓取——使用 PyQt4 从 JS 生成的页面中缺少 <dt> 标记的文本元素