Python中有没有办法手动退出“with”语句(上下文管理器)

时间:2014-07-22 15:42:49

标签: python pyqt pyside contextmanager

我正在使用PySide(Qt)Gui,它应该在按钮点击时退出循环。 PySide的按钮单击发出信号,信号调用连接的功能。但是,当信号调用函数时,它使用它自己的系统进行错误处理。

我真的不想对信号错误处理进行修复。

有没有办法打破""声明没有引起错误。

import sys
import time
from PySide import QtGui, QtCore

class MyClassError(Exception): pass

class MyClass(QtGui.QProgressDialog):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Properties
        self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint)
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.setMinimumDuration(2000) # if progress is finished in 2 sec it wont show
        self.setRange(0, 99)
    # end Constructor

    def breakOut(self):
        print("break out")
        self.__exit__(MyClassError, "User cancel!", "") # does not break out of "with"
        raise MyClassError("User cancel!") # PySide just prints Exception
    # end breakOut

    def __enter__(self):
        self.canceled.connect(self.breakOut)
        return self
    # end enter (with)

    def __exit__(self, etype, value, traceback):
#         self.canceled.disconnect(self.breakOut)
        if etype is None or etype == MyClassError:
            return True
        raise
    # end exit
# end class MyClass

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    window = QtGui.QMainWindow()
    window.show()

    myinst = MyClass()
    window.setCentralWidget(myinst)

    val = 0
    with myinst:
        for i in range(100):
            myinst.setLabelText(str(i))
            myinst.setValue(i)
            val = i+1
            time.sleep(0.1)
    # end with
    print(val)

    sys.exit(app.exec_())

我在信号中看到PySide / Qt错误处理的修复程序并不好看。它会覆盖QApplication,这意味着使用此类的其他人必须使用新的QApplication。

是否有一种简单的方法来解决PySide / Qt信号错误处理问题,或者是否有另一种方法可以打破""言。

2 个答案:

答案 0 :(得分:2)

首先,我已经解释了你对@ dano答案的评论意味着你想在with声明中做一些长时间运行的操作。

我认为你有一些基本的误解。

  1. 您的with语句(以及其中的代码)在调用app.exec_()之前。在调用app.exec_()之前,Qt事件循环不会开始运行。因此,所有with语句正在执行的操作是将事件循环启动后由事件循环处理的事件排队。因此,取消with语句中的代码的qt信号将永远不会起作用,因为在事件循环开始之前不会处理信号,这是循环结束后(在至少在上面提供的测试用例中(。

    解决此问题的方法是将with语句放在由QT事件循环执行的回调中(例如,由QTimer或类似的排队的函数)

  2. 即使你已经解决了上述问题,你也永远不会能够中断 单击按钮(或任何类型的信号)的长时间运行任务,因为长时间运行的任务与Qt事件循环在同一个线程中,因此Qt事件循环无法处理任何新命令(如按下取消按钮),直到你的长期任务完成了。你的长期任务是

    要解决此问题,您需要将长时间运行的任务放在线程(python线程或QThread)或其他进程中。但是,这些方法都有其自身的困难,主要集中在这样一个事实:您永远不应该尝试直接从线程更新GUI。有一些选项可以解决这个问题,请参阅herehere以获取从线程安全地更新GUI的问题。如果它有帮助,我已将这些内容包装在库中,称为qtutils

  3. 我意识到这并没有直接解决你如何打断with声明的问题,但是我希望我已经说清楚你当前的方法是行不通的。如果你切换到线程或进程,你应该能够立即杀死线程/进程(如果你愿意),而不是等待线程读取条件并退出自己(虽然当然硬杀死某些东西可能会产生意想不到的副作用,但既然你似乎想要这样做,我会假设你已经考虑过这个并且已经决定它会一直很好。)

答案 1 :(得分:0)

您可以检查它是否已在循环中取消:

class MyClass(QtGui.QProgressDialog):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.cancelled = False
        self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint)
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.setMinimumDuration(2000) # if progress is finished in 2 sec it wont show
        self.setRange(0, 99) 

    def breakOut(self):
        print("break out")
        self.cancelled = True

    def __enter__(self):
        self.canceled.connect(self.breakOut)
        return self

    def __exit__(self, etype, value, traceback):
        self.canceled.disconnect(self.breakOut)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    window = QtGui.QMainWindow()
    window.show()

    myinst = MyClass()
    window.setCentralWidget(myinst)

    val = 0 
    with myinst:
        for i in range(100):
            if myinst.cancelled:
                break
            myinst.setLabelText(str(i))
            myinst.setValue(i)
            val = i+1 
            time.sleep(0.1)

breakOut中引发异常将不会产生您想要的效果,因为它是在与for循环完全不同的上下文中调用的。无论PySide的错误处理如何,调用__exit__都不会生成with语句。我认为你有因果关系:__exit__当你离开一个阻止时被调用,调用__exit__不会强制with阻止中止。