首页 > 解决方案 > 当控制台输出重新路由到 GUI 时暂停 Python 代码

问题描述

我借用了在 stackoverflow 上找到的设计,将控制台输出重定向到 PyQt5 GUI textEdit 小部件。这工作正常,但文本不会“实时”显示。一旦进程完成,它似乎会将文本输出到 GUI。这一直不是问题,直到我尝试使用time.sleep(secs)打印一些东西,暂停,然后打印其他东西。最终发生的是程序暂停 for secs,然后一次打印所有语句。

此类位于 GUI 的 mainWindow 文件中:

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)

此函数在事件处理文件的主类中(外部__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 秒,然后将两个语句一起打印。

标签: pythontimesleeppause

解决方案


在为 GUI 代码编程时,程序的设计方式发生了根本性的转变。简而言之:在构建和初始化之后,程序一直在 GUI 框架提供的“事件循环”中运行,并且只有在发生特定事件时才会调用您的代码。

这与您的代码一直在运行的终端应用程序形成对比,您可以通过“time.sleep”告诉何时执行“打印”、“输入”和暂停。

GUI 代码负责记录事件(键盘、UI、网络等)、重绘窗口内容并调用您的代码以响应事件,或者仅在需要重绘您的代码(例如更新背景图像)。

因此,当控件传递回其事件循环时,它只能使用重定向的“打印”呈现应该显示在窗口中的文本。当你time.sleep暂停返回时 - 事件循环中没有代码运行,当然它不能做任何屏幕绘图。

需要的是,您在程序中编写暂停的方式是在暂停期间,GUI 事件循环正在运行 - 而不是“time.sleep”,它只会暂停整个线程。

在 Qt 中,这样做的方法是创建一个 QTimer 对象来调用您要用于在特定时刻打印文本的代码,然后通过从您的函数返回将执行交给 QtMainloop。

由于 Python 对嵌套函数的支持,这可以轻松完成,甚至lambda在设置计时器时使用函数。

...
print('This is the first message')
timer = QtCore.QTimer

timer.singleShot(5000, lambda *_: print('This should print 5 seconds later'))

应该适用于给定的示例。(与 UI 一样,该调用以毫秒而不是秒为单位占用暂停时间)。

如果您需要在每个短语输出后安排要打印的更多文本,则需要在回调本身内部调用调度,并且需要更复杂一些,但它仍然可以像这样简单:

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 语法)-我不在乎大约那时(我将参数序列称为“_”以表示我不在乎它是如何被调用的,因为它无论如何都不会被使用 - 这是一个编码约定))

iterandnext调用是比一个可能使用的更多的“Python 方言”,但是可以只使用一个列表和一个直到列表长度的计数器。


推荐阅读