PyQt 事件循环和 ipython 中的异常

Posted

技术标签:

【中文标题】PyQt 事件循环和 ipython 中的异常【英文标题】:Exceptions in PyQt event loop and ipython 【发布时间】:2017-03-29 06:18:39 【问题描述】:

我有一个显示一些小部件和按钮的 PyQt 程序。

我希望程序作为独立的 python 实例运行,或者在 ipython 环境中运行。在这种情况下,我在 Jupyter 控制台中使用了以下魔法命令(以前我在启动 ipython qtconsole 时必须使用 --gui=qt)

%pylab qt

为了有一个双向运行的程序,我的主模块有以下几行:

APP = QtGui.Qapplication.instance() # retrieves the ipython qt application if any
if APP is None:
    APP = QtGui.QApplication(["foo"]) # create one if standalone execution

if __name__=='__main__':
    APP.exec_() # Launch the event loop here in standalone mode 

这是我的问题: 事件循环生成的异常很难被用户检测到,因为它们会在后台控制台中弹出。我想捕捉事件循环中发生的任何异常,并显示警告(例如在 QMainWindow 状态栏中让用户知道发生了异常)。

我尝试了几种策略,但 PyQt 和 Ipython 的内部机制之间似乎有一个阴谋使这不可能:

重新实现 sys.excepthook(请参阅 Preventing PyQt to silence exceptions occurring in slots):由于 ipython 不断覆盖 sys.excepthook 而不工作 检测 IPython 是否正在运行,然后使用 IPYTHON.set_custom_exc (Opening an IPython shell on any (uncatched) exception):不幸的是,qt 事件循环异常不会触发处理程序。 覆盖QApplication.notify:运气不好,我打算在派生函数中调用的原生QApplication.notify函数没有抛出异常,返回值(布尔值)也没有反映槽的正确执行。这个线程中的答案很有趣:How to log uncatched exceptions of a QApplication?,然而,这个策略似乎在 Qt c++ 中有效,但是 notify 的 python 包装器只是将异常打印到控制台而不是引发它们。

这是一个困扰我很久的问题。有人有解决办法吗?

【问题讨论】:

尝试this answer中建议的解决方案。 我忘记了这一点:覆盖 IPython 将用作 sys.excepthook 的猴子补丁的函数(如您的回答中所建议的那样)也不起作用。事实上,带有 qt 选项的 Ipython 甚至会阻止 qt 事件循环调用 sys.excepthook。我不明白 IPython 开发人员在捕获事件循环异常时发现了什么如此淫秽?! 老实说,我认为在这里报告所有这些是浪费时间:您需要与 ipython 开发人员一起讨论。 谢谢,我在 ipython 的 github issue 页面发帖:github.com/ipython/ipython/issues/10057。与此同时,我也注意到了这个相关的问题。 ***.com/questions/26426419/…. 【参考方案1】:

实际上,开发人员的回答为我指明了正确的方向: 问题是每次执行 ipython 单元时,都会对新的 sys.excepthook 进行猴子补丁,一旦执行完成,sys.excepthook 就会恢复到前一个(参见 ipkernel/kernelapp.py)。

因此,在正常的 ipython 单元指令中更改 sys.excepthook 不会更改在 qt 事件循环期间执行的异常钩子。

一个简单的解决方案是在 qt 事件中对 sys.excepthook 进行monkeypatch:

from PyQt4 import QtCore, QtGui
import sys
from traceback import format_exception

def new_except_hook(etype, evalue, tb):
    QtGui.QMessageBox.information(None, 
                                  str('error'),
                                  ''.join(format_exception(etype, evalue, tb)))

def patch_excepthook():
    sys.excepthook = new_except_hook
TIMER = QtCore.QTimer()
TIMER.setSingleShot(True)
TIMER.timeout.connect(patch_excepthook)
TIMER.start()

这种方法的一个好处是它适用于独立和 ipython 执行。

我想人们也可以想象通过在每个小部件的 event_handler 中调用 patch_excepthook 来修改不同版本的 new_except_hook,具体取决于触发异常的小部件。

【讨论】:

以上是关于PyQt 事件循环和 ipython 中的异常的主要内容,如果未能解决你的问题,请参考以下文章

Qt(PyQt)事件循环中的xmlrpc?

ipython在最新python版本中出现事件循环问题

QCoreApplication::exec: 事件循环已经在运行 - 调用另一个 PyQt5 文件

编辑表时关闭 PyQt 事件循环

《PyQT5软件开发 - 基础篇》第5章 PyQt5事件和信号

《PyQT5软件开发 - 基础篇》第5章 PyQt5事件和信号