我从一位研究生那里继承了一段用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上运行。
答案 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()