Есть ли в 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
заявление.
Я думаю, у вас есть несколько фундаментальных недоразумений.
Ваш
with
оператор (и код внутри) перед вызовомapp.exec_()
, Цикл Qt Event не запускается доapp.exec_()
называется. Так что все вашиwith
Оператор делает ставит в очередь события, которые будут обработаны циклом событий после его запуска. Таким образом, сигнал qt для отмены кода в пределахwith
оператор никогда не сработает, потому что сигнал не будет обработан до тех пор, пока не будет запущен цикл обработки событий, то есть после того, как цикл завершится (по крайней мере, в тестовом примере, который вы указали выше (.Чтобы исправить это, поместите
with
оператор в обратном вызове, который выполняется циклом QT Event (например, функция, поставленная в очередьQTimer
или похожие)Даже если вы исправите вышеприведенное, вы никогда не сможете прервать долгосрочную задачу нажатием кнопки (или любым другим сигналом), потому что эта длинная задача находится в том же потоке, что и цикл 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
блок для отмены.