【问题标题】:Separating Progress Tracking and Loop Logic分离进度跟踪和循环逻辑
【发布时间】:2011-03-14 14:28:34
【问题描述】:

假设我想使用进度条打印机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)

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

你能想出一种方法来清晰地分离进度跟踪代码和功能代码吗? (可以有进度跟踪装饰器之类的吗?)

【问题讨论】:

    标签: python oop separation-of-concerns


    【解决方案1】:

    似乎这段代码将从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()
    

    【讨论】:

      【解决方案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
      

      【讨论】:

        【解决方案3】:

        我的做法是这样的:

        只要循环代码发生变化(或当它想要报告它),就会产生进度百分比。然后进度跟踪代码从生成器中读取,直到它为空;每次阅读后更新进度条。

        不过,这也有一些缺点:

        • 您需要一个没有进度条的函数来调用它,因为您仍然需要从生成器中读取直到它为空。
        • 最后不能轻易返回值。一个解决方案是包装返回值,以便进度方法可以确定函数是产生进度更新还是返回值。实际上,包装进度更新可能会更好,因此可以产生未包装的常规返回值 - 但这需要更多的包装,因为每次进度更新都需要进行一次包装。

        【讨论】:

          猜你喜欢
          • 2021-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多