从长期运行的函数中将stdout重定向到wxpython TextCtrl

时间:2018-07-15 06:15:21

标签: python wxpython

我从一位研究生那里继承了一段用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进行stdout重定向,但无法腌制TextCtrl。有什么建议可以解决吗?

不涉及更改LongSimulation的答案将是更可取的,但是如果无法实现或太难实现,请告诉我要使此方法生效需要进行哪些更改。任何帮助将不胜感激。

我正在使用python 2.7.9和wxPython 3.0.1.1,并且它在linux上运行。

1 个答案:

答案 0 :(得分:0)

一种简单的方法是对发布的代码使用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()