使用生成器作为进度通知器
我现在在用生成器来快速获取一些长时间处理的进度,不过我觉得这种方式不是很优雅,想知道通常是怎么做的。
让我先解释一下,我有一个叫 engine.py 的模块,用来处理视频(比如分割、背景/前景减法等等),这个过程需要花费很多时间,从几秒到几分钟不等。
我通过一个用 wxpython 写的图形界面和一个控制台脚本来使用这个模块。当我查找如何在 wxpython 中实现进度对话框时,我发现必须要有一个进度值来更新我的对话框,这个逻辑你肯定能理解……
所以我决定在我的引擎函数中使用处理的帧数,每处理 33 帧就返回当前帧数,处理完成时返回 None。
这样做的效果是这样的:
dlg = wx.ProgressDialog("Movie processing", "Movie is being written...",
maximum = self.engine.endProcessingFrame,self.engine.startProcessingFrame,
parent=self,
style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_SMOOTH | wx.PD_CAN_ABORT)
state = self.engine.processMovie()
f = state.next()
while f != None:
c, s = dlg.Update(f, "Processing frame %d"%f)
if not c:break
f = state.next()
dlg.Destroy()
这样做效果很好,速度上没有明显的损失,但我希望能在不使用生成器的情况下调用 processMovie() 函数,如果我不想的话。
比如说,我的控制台脚本使用这个引擎模块时并不关心进度,我可以使用它,但它是打算在没有显示的环境中执行的,所以我真的不在乎进度……
有没有人有其他的设计方案,能比我想到的更好?(比如使用线程、全局变量、进程等等)
我觉得应该有某种设计能更干净地完成这个工作 :-)
3 个回答
首先,如果你使用生成器,那就可以把它当作迭代器来用:
state = self.engine.processMovie()
for f in state:
c, s = dlg.Update(f, "Processing frame %d"%f)
if not c:
break
dlg.Destroy()
而且不要返回 None
;当你完成时就停止返回,直接离开这个函数;或者可以抛出 StopIteration
。这是结束生成的正确方式(而且在使用 for
循环时,这是必须的)。
除此之外,我觉得这个想法很好。在我看来,这是一种非常合理的使用生成器的方法。
你可能想把 33
这个数字变得可配置(也就是说,可以作为参数传给 processMovie
);33这个数字看起来是随便选的,如果你处理的是一部两个小时的电影,我想就没必要每33帧更新一次进度条了。
这听起来非常适合用事件来处理。这个过程会发送一个“状态更新事件”,而任何想要了解这个状态的人(在这个例子中是对话框)都会去监听这个事件。
使用生成器来处理这个问题是可以的,但使用生成器的主要好处是你可以使用内置的语法:
for f in self.engine.processMovie():
c, s = dlg.Update(f, "Processing frame %d"%f)
if not c: break
如果你不在乎这个,那你可以选择:
for f in self.engine.processMovie(): pass
或者提供一个函数(比如 engine.processMovieFull)来帮你完成这个任务。
你也可以使用普通的回调函数:
def update_state(f):
c, s = dlg.Update(f, "Processing frame %d"%f)
return c
self.engine.processMovie(progress=update_state)
... 但是如果你想逐步处理数据,这样就没那么好用了;回调模型更喜欢一次性完成所有工作——这就是生成器的真正好处。