当控制台输出重新路由到 GUI 时暂停 Python 代码
Pausing Python Code When Console Output Has Been Re-Routed to GUI
我借用了我在 Whosebug 上找到的一个设计,将控制台输出重定向到 PyQt5 GUI textEdit 小部件。这工作正常,但文本未显示在 "real-time" 中。似乎在进程完成后将文本输出到 GUI。这一直不是问题,直到我尝试使用 time.sleep(secs)
打印一些东西,暂停,然后打印其他东西。最终发生的是程序暂停 secs
,然后一次打印所有语句。
此 class 在 GUI 的主窗口文件中:
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
这是在我的事件处理文件的 __init__
方法中:
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.case_setup_console.setReadOnly(True)
self.main_console.setReadOnly(True)
这个函数在事件处理文件的主要class中(在__init__
之外):
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.case_setup_console.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.case_setup_console.setTextCursor(cursor)
self.case_setup_console.ensureCursorVisible()
这按预期工作,将输出重新路由到文本编辑小部件 self.case_setup_console
。但是,当我尝试 运行 代码时,例如:
print('This is the first message')
time.sleep(5)
print('This should print 5 seconds later')
程序等待 5 秒,然后同时打印两条语句。
在为 GUI 代码编程时,程序的设计方式发生了根本性的转变。简而言之:构建和初始化后,程序始终运行在GUI框架提供的"event loop"中,只有在特定事件发生时才会调用您的代码地点。
这与您的代码始终处于 运行ning 的终端应用程序形成对比,您告诉何时执行 "print"s、"input"s 并使用 [ 暂停=42=].
GUI代码负责记录事件(键盘、UI、网络等...),重绘window内容并调用您的代码作为响应到事件中,或者在需要重新绘制代码中定义的内容时(如更新背景图像)。
因此,当控件被传回其事件循环时,它只能呈现本应显示在 window 中的文本,以及您重定向的 "print"。当您执行 time.sleep
时,您会暂停 return - 事件循环中没有代码 运行,当然,它不能进行任何屏幕绘制。
需要的是在程序中以一种方式编写暂停,在暂停期间,GUI 事件循环是 运行ning - 而不是 "time.sleep",即只是暂停你的整个线程。
在 Qt 中,这样做的方法是创建一个 QTimer 对象来调用要用于在特定时刻打印文本的代码,然后通过 returning 将执行交给 QtMainloop从你的功能。
感谢 Python 对嵌套函数的支持,这可以轻松完成,甚至在设置计时器本身时使用 lambda
函数。
...
print('This is the first message')
timer = QtCore.QTimer
timer.singleShot(5000, lambda *_: print('This should print 5 seconds later'))
应该适用于给定的示例。 (调用,像往常一样 UIs,暂停时间以毫秒为单位,而不是以秒为单位)。
如果您需要安排在每个短语输出后打印更多文本,您将需要在回调本身内部调用调度,并且需要更复杂一些,但它仍然可以像这样简单:
phrases = iter(("hello", "world", "I", "am", "talking", "slowly!"))
timer = QtCore.QTimer()
def talker(*_):
phrase = next(phrases, None)
if not phrase:
return
print(phrase)
timer.singleShot(1000, talker)
timer.singleShot(1000, talker)
(请注意,*_
参数名称也没有什么特别之处:我只是表示回调可能有任意数量的位置参数 - (“*”部分,即 Python 语法)——那时我不会关心(我将参数序列称为“_”以表明我不关心它是如何被调用的,因为它无论如何都不会被使用——这是一个编码约定) )
iter
和 next
调用比可能使用的要多 "Python dialect",但可以只使用一个列表和一个不超过列表长度的计数器。
我借用了我在 Whosebug 上找到的一个设计,将控制台输出重定向到 PyQt5 GUI textEdit 小部件。这工作正常,但文本未显示在 "real-time" 中。似乎在进程完成后将文本输出到 GUI。这一直不是问题,直到我尝试使用 time.sleep(secs)
打印一些东西,暂停,然后打印其他东西。最终发生的是程序暂停 secs
,然后一次打印所有语句。
此 class 在 GUI 的主窗口文件中:
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
这是在我的事件处理文件的 __init__
方法中:
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.case_setup_console.setReadOnly(True)
self.main_console.setReadOnly(True)
这个函数在事件处理文件的主要class中(在__init__
之外):
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.case_setup_console.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.case_setup_console.setTextCursor(cursor)
self.case_setup_console.ensureCursorVisible()
这按预期工作,将输出重新路由到文本编辑小部件 self.case_setup_console
。但是,当我尝试 运行 代码时,例如:
print('This is the first message')
time.sleep(5)
print('This should print 5 seconds later')
程序等待 5 秒,然后同时打印两条语句。
在为 GUI 代码编程时,程序的设计方式发生了根本性的转变。简而言之:构建和初始化后,程序始终运行在GUI框架提供的"event loop"中,只有在特定事件发生时才会调用您的代码地点。
这与您的代码始终处于 运行ning 的终端应用程序形成对比,您告诉何时执行 "print"s、"input"s 并使用 [ 暂停=42=].
GUI代码负责记录事件(键盘、UI、网络等...),重绘window内容并调用您的代码作为响应到事件中,或者在需要重新绘制代码中定义的内容时(如更新背景图像)。
因此,当控件被传回其事件循环时,它只能呈现本应显示在 window 中的文本,以及您重定向的 "print"。当您执行 time.sleep
时,您会暂停 return - 事件循环中没有代码 运行,当然,它不能进行任何屏幕绘制。
需要的是在程序中以一种方式编写暂停,在暂停期间,GUI 事件循环是 运行ning - 而不是 "time.sleep",即只是暂停你的整个线程。
在 Qt 中,这样做的方法是创建一个 QTimer 对象来调用要用于在特定时刻打印文本的代码,然后通过 returning 将执行交给 QtMainloop从你的功能。
感谢 Python 对嵌套函数的支持,这可以轻松完成,甚至在设置计时器本身时使用 lambda
函数。
...
print('This is the first message')
timer = QtCore.QTimer
timer.singleShot(5000, lambda *_: print('This should print 5 seconds later'))
应该适用于给定的示例。 (调用,像往常一样 UIs,暂停时间以毫秒为单位,而不是以秒为单位)。
如果您需要安排在每个短语输出后打印更多文本,您将需要在回调本身内部调用调度,并且需要更复杂一些,但它仍然可以像这样简单:
phrases = iter(("hello", "world", "I", "am", "talking", "slowly!"))
timer = QtCore.QTimer()
def talker(*_):
phrase = next(phrases, None)
if not phrase:
return
print(phrase)
timer.singleShot(1000, talker)
timer.singleShot(1000, talker)
(请注意,*_
参数名称也没有什么特别之处:我只是表示回调可能有任意数量的位置参数 - (“*”部分,即 Python 语法)——那时我不会关心(我将参数序列称为“_”以表明我不关心它是如何被调用的,因为它无论如何都不会被使用——这是一个编码约定) )
iter
和 next
调用比可能使用的要多 "Python dialect",但可以只使用一个列表和一个不超过列表长度的计数器。