使用生成器作为进度通知器

2 投票
3 回答
955 浏览
提问于 2025-04-15 13:18

我现在在用生成器来快速获取一些长时间处理的进度,不过我觉得这种方式不是很优雅,想知道通常是怎么做的。

让我先解释一下,我有一个叫 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 个回答

0

首先,如果你使用生成器,那就可以把它当作迭代器来用:

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帧更新一次进度条了。

1

这听起来非常适合用事件来处理。这个过程会发送一个“状态更新事件”,而任何想要了解这个状态的人(在这个例子中是对话框)都会去监听这个事件。

2

使用生成器来处理这个问题是可以的,但使用生成器的主要好处是你可以使用内置的语法:

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)

... 但是如果你想逐步处理数据,这样就没那么好用了;回调模型更喜欢一次性完成所有工作——这就是生成器的真正好处。

撰写回答