PyQt事件处理程序捕获异常

7
这里是一个展示问题的代码示例:
from PyQt4 import QtGui
app = QtGui.QApplication([])
dialog = QtGui.QDialog()
button = QtGui.QPushButton('I crash')
layout = QtGui.QHBoxLayout()
layout.addWidget(button)
dialog.setLayout(layout)
def crash(): raise Exception('Crash!')
button.clicked.connect(crash)
button.click()
print 'I should not happen'

当我运行时,PyQt4会处理错误。我的控制台显示了一个带有“Crash!”等内容的堆栈跟踪,并且我看到“不应该发生”的字样。
这并不是很有用,因为我得到了一个具有许多处理程序的大型应用程序,我需要将所有错误都强制上升到我的界面(以及我的-咳咳-自动化测试)。每次运行时,错误都会逃脱我的网,它们需要过多且无用的try:except块,在每个处理程序中进行才能捕获它们。
换句话说,我希望好代码非常好,而坏代码非常糟糕。不是掩盖。
如果已经提出了这个问题,我表示歉意,但当我进行电子搜索时,我自然会得到成千上万个新手提出基本的错误处理问题(或者更糟糕的是,我得到新手询问如何关闭他们不良异常的问题!)
我如何覆盖PyQt4的默认错误处理,以便我可以自己传播或记录错误?请不要回答sys.excepthook - 它会捕获PyQt4没有捕获的错误。

1
我认为你的问题在于异常发生在PyQt事件循环运行的地方(我认为这是一个不同的线程),因此你实际上是在询问如何在线程之间移动异常。 - tacaswell
即使它没有使用多个线程,它仍然在运行事件循环机制(可以将函数执行移动到不同的线程),因此我认为问题仍然是在线程之间移动异常,只是源和目标相同。 - tacaswell
1
你有尝试过使用 sys.excepthook 吗?它可以捕获那个异常。 - Avaris
打印 threading.currentThread() 返回相同的对象,无论是在 .click() 之前还是在 def crash() 内部。 (而且,一般来说,窗口架构是单线程和事件驱动的。) - Phlip
你最终解决了这个问题吗? - tacaswell
显示剩余2条评论
2个回答

3
这不是答案,这个网站强制我们适应一个理想化的主题模板的预设观念,太傻了。
解决方法是使用我的测试框架setUp()来连接异常处理程序:
def setUp(self):
    self.no_exceptions = True

    def testExceptionHook(type, value, tback):
        self.no_exceptions = False
        sys.__excepthook__(type, value, tback)

    sys.excepthook = testExceptionHook

在那里写着“self.no_exceptions = False”的地方,我更愿意简单地说self.fail('')。然而,由于Python的单元测试库坚持抛出异常来注册测试失败,并且因为PyQt坚持窃取所有异常,我们陷入了僵局。

为了适应unittest.TestCase的傻瓜式预设的理想化测试用例,我必须设置一个变量,然后在拆卸中检测它:

def tearDown(self):
    self.assertTrue(self.no_exceptions)

虽然这种方法还不完美,但至少可以迫使我花更多的时间关注错误,而不是花时间在技术网站上抱怨。

根本问题:如何关闭PyQt的魔法错误处理程序?- 仍然没有答案...


2
我认为答案是这不是PyQt的“功能”,而是一个内在的设计后果,它让信号/槽工作(请记住信号/槽通信也通过c++层进行)。
这很丑陋,但可以解决你的问题。
from PyQt4 import QtGui
import time
app = QtGui.QApplication([])

class exception_munger(object):
    def __init__(self):
        self.flag = True
        self.txt = ''
        self.type = None

    def indicate_fail(self,etype=None, txt=None):
        self.flag = False
        if txt is not None:
            self.txt = txt
        self.type = etype
    def reset(self):

        tmp_txt = self.txt
        tmp_type = self.type
        tmp_flag = self.flag
        self.flag = True
        self.txt = ''
        self.type = None
        return tmp_flag, tmp_type, tmp_txt

class e_manager():
    def __init__(self):
        self.old_hook = None

    def __enter__(self):
        em = exception_munger()
        def my_hook(type, value, tback):
            em.indicate_fail(type, value)
            sys.__excepthook__(type, value, tback) 
        self.old_hook = sys.excepthook
        sys.excepthook = my_hook
        self.em = em
        return self

    def __exit__(self,*args,**kwargs):
        sys.excepthook = self.old_hook

def mang_fac():
    return e_manager()

def assert_dec(original_fun):

    def new_fun(*args,**kwargs):
        with mang_fac() as mf:
            res = original_fun(*args, **kwargs)
            flag, etype, txt = mf.em.reset()
            if not flag:
                raise etype(txt)
            return res
    return new_fun


@assert_dec
def my_test_fun():
    dialog = QtGui.QDialog()
    button = QtGui.QPushButton('I crash')
    layout = QtGui.QHBoxLayout()
    layout.addWidget(button)
    dialog.setLayout(layout)
    def crash(): 
        time.sleep(1)
        raise Exception('Crash!')
    button.clicked.connect(crash)


    button.click()

my_test_fun()
print 'should not happen'

这将不会打印出“应该不会发生”的内容,并且会提供一些可以在自动化测试中捕获的东西(使用正确的异常类型)。

In [11]: Traceback (most recent call last):
  File "/tmp/ipython2-3426rwB.py", line 68, in crash
Exception: Crash!
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-11-6ef4090ab3de> in <module>()
----> 1 execfile(r'/tmp/ipython2-3426rwB.py') # PYTHON-MODE

/tmp/ipython2-3426rwB.py in <module>()

/tmp/ipython2-3426rwB.py in new_fun(*args, **kwargs)

Exception: Crash!

In [12]: 

堆栈跟踪有些混乱,但你仍然可以阅读第一个被打印出来的。

1
tx - 我确实使用了错误的sys.excepthook。然而...无论我在钩子内写什么,似乎都无法跳出事件处理程序。我必须放弃编写一个程序的目标,即如果它有未处理的异常就会崩溃。我必须将我的目标降低到只在测试框架中充分捕获异常。而且因为测试已经有了框架,所以我不需要你的包装器...但根本问题仍然没有得到解答——如何关闭这个PyQt功能。 - Phlip

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接