分离进度跟踪和循环逻辑

时间:2011-03-14 14:28:35

标签: python oop separation-of-concerns

假设我想使用进度条打印机ProgressMeter跟踪循环的进度(如此recipe中所述)。

def bigIteration(collection):
    for element in collection:
        doWork(element)

我希望能够打开和关闭进度条。出于性能原因,我还想每x步更新一次。我天真的做法是

def bigIteration(collection, progressbar=True):
    if progressBar:
        pm = progress.ProgressMeter(total=len(collection))
        pc = 0
    for element in collection:
        if progressBar:
            pc += 1
            if pc % 100 = 0:
                pm.update(pc)
        doWork(element)

然而,我并不满意。从“美学”的角度来看,循环的功能代码现在被“污染”了一般的进度跟踪代码。

您能想到一种干净地分离进度跟踪代码和功能代码的方法吗? (可以有进度跟踪装饰器吗?)

3 个答案:

答案 0 :(得分:6)

看起来这段代码会受益于null object pattern

# a progress bar that uses ProgressMeter
class RealProgressBar:
     pm = Nothing
     def setMaximum(self, max):
         pm = progress.ProgressMeter(total=max)
         pc = 0
     def progress(self):
        pc += 1
        if pc % 100 = 0:
            pm.update(pc)

# a fake progress bar that does nothing
class NoProgressBar:
    def setMaximum(self, max):
         pass 
    def progress(self):
         pass

# Iterate with a given progress bar
def bigIteration(collection, progressBar=NoProgressBar()):
    progressBar.setMaximum(len(collection))
    for element in collection:
        progressBar.progress()
        doWork(element)

bigIteration(collection, RealProgressBar())

(原谅我的法语,呃,Python,这不是我的母语;)希望你能得到这个想法。)

这使您可以从循环中移动进度更新逻辑,但是仍然有一些与进度相关的调用。

如果您从集合中创建一个生成器,可以在迭代时自动跟踪进度,则可以删除此部件。

 # turn a collection into one that shows progress when iterated
 def withProgress(collection, progressBar=NoProgressBar()):
      progressBar.setMaximum(len(collection))
      for element in collection:
           progressBar.progress();
           yield element

 # simple iteration function
 def bigIteration(collection):
    for element in collection:
        doWork(element)

 # let's iterate with progress reports
 bigIteration(withProgress(collection, RealProgressBar()))

这种方法使您的bigIteration功能保持原样,并且具有高度可组合性。例如,假设你也想要添加取消你的这个大的迭代。只需创建恰好可以取消的另一个生成器。

# highly simplified cancellation token
# probably needs synchronization
class CancellationToken:
     cancelled = False
     def isCancelled(self):
         return cancelled
     def cancel(self):
         cancelled = True

# iterates a collection with cancellation support
def withCancellation(collection, cancelToken):
     for element in collection:
         if cancelToken.isCancelled():
             break
         yield element

progressCollection = withProgress(collection, RealProgressBar())
cancellableCollection = withCancellation(progressCollection, cancelToken)
bigIteration(cancellableCollection)

# meanwhile, on another thread...
cancelToken.cancel()

答案 1 :(得分:2)

您可以将bigIteration重写为生成器函数,如下所示:

def bigIteration(collection):
    for element in collection:
        doWork(element)
        yield element

然后,你可以在此之外做很多事情:

def mycollection = [1,2,3]
if progressBar:
    pm = progress.ProgressMeter(total=len(collection))
    pc = 0
    for item in bigIteration(mycollection):
        pc += 1
        if pc % 100 = 0:
            pm.update(pc)
else:
    for item in bigIteration(mycollection):
        pass

答案 2 :(得分:1)

我的方法就是这样:

循环代码会在每次更改时(或每当要报告时)生成进度百分比。然后,进度跟踪代码从生成器读取,直到它为空;每次阅读后更新进度条。

然而,这也有一些缺点:

  • 您需要一个函数来调用它而不需要进度条,因为您仍然需要从生成器读取它直到它为空。
  • 最后您无法轻易返回值。解决方案是包装返回值,因此progress方法可以确定函数是否产生了进度更新或返回值。实际上,包装进度更新可能更好,因此可以解包常规返回值 - 但这需要更多的包装,因为它需要为每个进度更新而不是一次。
相关问题