分离进度跟踪和循环逻辑

发布于 2024-10-22 03:47:32 字数 737 浏览 2 评论 0原文

假设我想使用进度条打印机 ProgressMeter 跟踪循环的进度(如本 食谱)。

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)

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

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

Suppose i want to track the progress of a loop using the progress bar printer ProgressMeter (as described in this recipe).

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

I would like to be able to switch the progress bar on and off. I also want to update it only every x steps for performance reasons. My naive way to do this is

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)

However, I am not satisfied. From an "aesthetic" point of view, the functional code of the loop is now "contaminated" with generic progress-tracking code.

Can you think of a way to cleanly separate progress-tracking code and functional code? (Can there be a progress-tracking decorator or something?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

醉态萌生 2024-10-29 03:47:32

看起来这段代码会受益于空对象模式

# 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()

It seems like this code would benefit from the 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())

(Pardon my French, er, Python, it's not my native language ;) Hope you get the idea, though.)

This lets you move the progress update logic from the loop, but you still have some progress related calls in there.

You can remove this part if you create a generator from the collection that automatically tracks progress as you iterate it.

 # 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()))

This approach leaves your bigIteration function as is and is highly composable. For example, let's say you also want to add cancellation this big iteration of yours. Just create another generator that happens to be cancellable.

# 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()
第几種人 2024-10-29 03:47:32

您可以将 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

You could rewrite bigIteration as a generator function as follows:

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

Then, you could do a great deal outside of this:

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
动听の歌 2024-10-29 03:47:32

我的方法是这样的:

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

然而,这也有一些缺点:

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

My approach would be like that:

The looping code yields the progress percentage whenever it changes (or whenever it wants to report it). The progress-tracking code then reads from the generator until it's empty; updating the progress bar after every read.

However, this also has some disadvantages:

  • You need a function to call it without a progress bar as you still need to read from the generator until it's empty.
  • You cannot easily return a value at the end. A solution would be wrapping the return value though so the progress method can determine if the function yielded a progress update or a return value. Actually, it might be nicer to wrap the progress update so the regular return value can be yielded unwrapped - but that'd require much more wrapping since it would need to be done for every progress update instead just once.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文