Разделение отслеживания прогресса и логики цикла - PullRequest
3 голосов
/ 14 марта 2011

Предположим, я хочу отслеживать ход цикла, используя индикатор выполнения 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)

Однако я не удовлетворен. С «эстетической» точки зрения функциональный код цикла теперь «загрязнен» общим кодом отслеживания прогресса.

Можете ли вы придумать способ четко разделить код отслеживания прогресса и функциональный код? (Может ли быть декоратор отслеживания прогресса или что-то?)

Ответы [ 3 ]

6 голосов
/ 14 марта 2011

Кажется, что этот код выиграл бы от шаблона нулевого объекта .

# 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 голосов
/ 14 марта 2011

Вы можете переписать 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
1 голос
/ 14 марта 2011

Мой подход был бы таким:

Код зацикливания выдает процент выполнения всякий раз, когда он изменяется (или всякий раз, когда он хочет сообщить об этом).Затем код отслеживания прогресса читает из генератора, пока он не станет пустым;обновление индикатора выполнения после каждого чтения.

Однако это также имеет ряд недостатков:

  • Вам нужна функция, чтобы вызывать ее без индикатора выполнения, поскольку вам все еще нужно читать изгенератор, пока он не пуст.
  • Вы не можете легко вернуть значение в конце.Решением было бы обернуть возвращаемое значение, чтобы метод выполнения мог определить, выдала ли функция обновление прогресса или возвращаемое значение.На самом деле, может быть лучше обернуть обновление прогресса, чтобы обычное возвращаемое значение можно было развернуть, но это потребовало бы гораздо большего обтекания, поскольку это нужно было бы делать для каждого обновления прогресса вместо одного раза.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...