4

我有一个显示一些小部件和按钮的 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(请参阅防止 PyQt 使插槽中发生的异常静音):不起作用,因为 ipython 不断覆盖 sys.excepthook
  • 检测 IPython 是否正在运行,然后使用 IPYTHON.set_custom_exc(在任何(未捕获的)异常上打开 IPython shell):不幸的是,qt 事件循环异常不会触发处理程序。
  • 覆盖 QApplication.notify:运气不好,我打算在派生函数中调用的原生 QApplication.notify 函数没有抛出异常,返回值(布尔值)也没有反映插槽的正确执行。这个线程中的答案很有趣:如何记录 QApplication 的未捕获异常?但是,这种策略似乎在 Qt c++ 中有效,但是 notify 的 python 包装器只是将异常打印到控制台而不是引发它们。

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

4

1 回答 1

5

实际上,开发人员的回答为我指明了正确的方向:问题是每次执行 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。

于 2016-11-18T10:06:10.743 回答