Есть ли в Python способ вручную выйти из оператора "with" (менеджер контекста)

Я использую 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 или есть альтернативный способ выхода из оператора with.

2 ответа

Решение

Во-первых, я интерпретировал ваш комментарий к ответу @dano так, что вы хотите выполнять какие-то длительные операции внутри with заявление.

Я думаю, у вас есть несколько фундаментальных недоразумений.

  1. Ваш with оператор (и код внутри) перед вызовом app.exec_(), Цикл Qt Event не запускается до app.exec_() называется. Так что все ваши with Оператор делает ставит в очередь события, которые будут обработаны циклом событий после его запуска. Таким образом, сигнал qt для отмены кода в пределах with оператор никогда не сработает, потому что сигнал не будет обработан до тех пор, пока не будет запущен цикл обработки событий, то есть после того, как цикл завершится (по крайней мере, в тестовом примере, который вы указали выше (.

    Чтобы исправить это, поместите with оператор в обратном вызове, который выполняется циклом QT Event (например, функция, поставленная в очередь QTimer или похожие)

  2. Даже если вы исправите вышеприведенное, вы никогда не сможете прервать долгосрочную задачу нажатием кнопки (или любым другим сигналом), потому что эта длинная задача находится в том же потоке, что и цикл Qt Event, и, следовательно, Qt Цикл обработки событий не может обрабатывать какие-либо новые команды (например, нажатие кнопки отмены) до тех пор, пока ваше долгосрочное задание не завершится. Пока ваше долгосрочное задание

    Чтобы это исправить, вам нужно поместить долгосрочную задачу в поток (поток Python или QThread) или другой процесс. Однако эти подходы сопряжены со своими собственными трудностями, в основном из-за того, что вы никогда не должны пытаться обновить графический интерфейс непосредственно из потока. Есть несколько вариантов, чтобы обойти это, см. Здесь и здесь вопросы, которые безопасно обновляют графический интерфейс из потока. Я поместил некоторые из этих вещей в библиотеку, если это поможет, называемую qtutils.

Я понимаю, что это не решает вопрос о том, как прервать with Заявление, однако я надеюсь, что я дал понять, что ваш нынешний подход не будет работать. Если вы переключаетесь на потоки или процессы, вы должны иметь возможность немедленно завершить поток / процесс (если хотите), вместо того, чтобы ждать, пока поток прочитает условие и выйдет сам (хотя, конечно, жесткое уничтожение чего-либо может иметь непредвиденные побочные эффекты)., но поскольку это то, что вы, похоже, хотите сделать, я предполагаю, что вы подумали об этом и решили, что это всегда будет хорошо).

Вы можете просто проверить, отменено ли это в самом цикле:

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 петля. призвание __exit__ не будет делать with утверждение, независимо от обработки ошибок PySide. Я думаю, что у вас есть причинно-следственная связь: __exit__ вызывается, когда вы покидаете блок с вызовом __exit__ не заставит with блок для отмены.

Другие вопросы по тегам