【问题标题】:Redirecting stdout to wxpython TextCtrl from a long running function将标准输出从长时间运行的函数重定向到 wxpython TextCtrl
【发布时间】:2018-07-15 06:15:21
【问题描述】:

我从一名研究生那里继承了一段用 python 编写的模拟脚本。它每隔 1 秒将其进度打印到终端上,让我们知道它正在运行,偶尔还会对结果进行一些部分总结。一切正常。

然后我们小组决定为这个脚本制作一个简单的 GUI。我已将输出重定向到 textctrl 类,以便进度可以显示在窗口中,而不是停留在终端上。以下是我所写内容的一个非常简化的版本:

import threading
import sys
import wx
import time

def LongSimulation(input_):
    # Simulate the behavior of the simulation code that I have
    # Actually it returns more than just the progress
    # Occasionally it also returns some partial summary result for diagnostic
    for i in range(0, 100):
        sys.stdout.write('Progress: %3.0f\r' % float(i))
        sys.stdout.flush()
        time.sleep(1)
    # return the simulation result
    return 'answer'

def LongSimulationWrapper(q, input_):
    result = LongSimulation(input_)
    q.put(result)

# subclass of TextCtrl. Just to put the flush method back in 
class TextCtrlPipe(wx.TextCtrl):
    def __init__(*args, **kargs):
        wx.TextCtrl.__init__(*args, **kargs)

    def flush(self):
        self.Refresh()


class MyForm(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None)

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL

        sizer = wx.BoxSizer(wx.VERTICAL)

        self.log = TextCtrlPipe(panel, wx.ID_ANY, size=(300,100), style=style)
        sizer.Add(self.log, 1, wx.ALL|wx.EXPAND, 5)

        btn = wx.Button(panel, wx.ID_ANY, 'Start')
        self.Bind(wx.EVT_BUTTON, self.onButton, btn)

        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

    def onButton(self, event):
        # redirect output to TextCtrl
        sys.stdout = self.log
        q = queue.Queue()
        # simulation input. I just randomly choose a number to demonstrate
        input_ = 0
        thread = threading.Thread(target=LongSimulationWrapper, args=(q, input_))
        thread.setDaemon(True)
        thread.start()
        # Getting returned result somehow halt the printing of progress
        #print(q.get())

# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

但是我马上遇到了一些麻烦。当我尝试使用队列来获取返回的结果时,就会出现问题。如果我取消注释行 print(q.get()),则进度不再实时显示。相反,只有在计算完成后才会显示所有内容,这违背了进度条的目的。

我知道 wx.ProgressDialog 类。但是,这不仅仅是我们需要的进展,还需要诊断的部分总结结果。我们仍然需要找到一种方法来重定向所有文本。真正的“LongSimulation”实际上相当复杂,我不完全了解它的内部工作,所以我不愿意改变他们模块的任何部分。鉴于这样的约束,我应该怎么做才能让它发挥作用?

我们还计划在未来整合多处理。 LongSimulation 是一个令人尴尬的并行模拟(具体来说是蒙特卡洛),所以我可以在不同的进程上运行相同的东西并合并结果。假设我们在 n 个核心上运行它,想法是创建 n 个 TextCtrl,每个核心都独立显示每个进程的进度,但我也无法让它工作。我尝试使用多处理并将单个 TextCtrl 传递给 LongSimulationWrapper 以进行标准输出重定向,但 TextCtrl 不能被腌制。有什么建议我可以解决吗?

不涉及更改 LongSimulation 的答案将是首选,但如果不可能或太难实现,请告诉我需要更改哪些内容才能使其正常工作。任何帮助将不胜感激。

我使用的是 python 2.7.9 和 wxPython 3.0.1.1,它在 linux 上运行。

【问题讨论】:

    标签: python wxpython


    【解决方案1】:

    一种简单的方法是使用wx.GetApp().Yield() 作为您发布的代码。

    对于multiprocessing 方法,请在此处查看接受的答案:
    How to capture output of a shell script running in a separate process, in a wxPython TextCtrl?

    这是最简单的方法:

    import wx
    import time
    
    def LongSimulation(self,input_):
        # Simulate the behavior of the simulation code that I have
        # Actually it returns more than just the progress
        # Occasionally it also returns some partial summary result for diagnostic
        for i in range(0, 10):
            self.log.write('Progress: %3.0f\r' % float(i))
            time.sleep(1)
            wx.GetApp().Yield()
        return 'answer'
    
    class MyForm(wx.Frame):
    
        def __init__(self):
            wx.Frame.__init__(self, None)
    
            # Add a panel so it looks the correct on all platforms
            panel = wx.Panel(self, wx.ID_ANY)
            style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL
    
            sizer = wx.BoxSizer(wx.VERTICAL)
    
            self.log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100), style=style)
            sizer.Add(self.log, 1, wx.ALL|wx.EXPAND, 5)
    
            btn = wx.Button(panel, wx.ID_ANY, 'Start')
            self.Bind(wx.EVT_BUTTON, self.onButton, btn)
    
            sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
            panel.SetSizer(sizer)
    
        def onButton(self, event):
            input_ = 0
            res = LongSimulation(self,input_)
            self.log.write(res)
            self.log.write('\n')
    
    # Run the program
    if __name__ == "__main__":
        app = wx.App(False)
        frame = MyForm()
        frame.Show()
        app.MainLoop()
    

    【讨论】:

    • 感谢您的回答。是的,Yield 功能就像一个魅力。您提供的多处理示例也有很大帮助。我对多处理示例的唯一问题是示例代码创建了无效的进程。我试图在最后加入所有过程,但 GUI 停止实时更新。这不是什么大问题,因为已失效的流程并没有真正伤害到我,但如果您能解释为什么我不能加入该流程以及解决它的方法,那就太好了。提前非常感谢
    • 我提供了链接作为示例,我没有运行或测试它。根据我的经验,最好不要过度复杂的代码。如果您打算在将来更改它,将来重写它,您的计划可能会改变。问候。
    猜你喜欢
    • 1970-01-01
    • 2011-07-26
    • 2015-10-21
    • 1970-01-01
    • 2016-09-07
    • 2015-08-31
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    相关资源
    最近更新 更多