Зачем нужны индикаторы прогресса?


Индикаторы прогресса (progress bar) — визуальное отображение процесса работы. Они избавляют нас от необходимости беспокоиться о том, не завис ли скрипт, дают интуитивное представление о скорости его выполнения и подсказывают, сколько времени осталось до завершения.


Человек ранее не использовавший индикаторы прогресса может предположить, что их внедрение может сильно усложнить код. К счастью, это не так. Небольшие примеры ниже покажут, как быстро и просто начать отслеживать прогресс в консоли или в интерфейсе быстро набирающей популярность графической библиотеки PySimpleGUI.


Используем Progress


Первым у нас идёт модуль Progress.


Всё, что от вас потребуется, это указать количество ожидаемых итераций, тип индикатора и вызывать функцию при каждой итерации:


import time
from progress.bar import IncrementalBar

mylist = [1,2,3,4,5,6,7,8]

bar = IncrementalBar('Countdown', max = len(mylist))

for item in mylist:
    bar.next()
    time.sleep(1)

bar.finish()

Результат работы:



Есть индикаторы на любой вкус:



Используем tqdm


Следующей на очереди идёт библиотека tqdm.


Быстрый и расширяемый индикатор прогресса для Python и CLI


Всего один вызов функции понадобится для получения результата аналогичного предыдущему:


import time
from tqdm import tqdm

mylist = [1,2,3,4,5,6,7,8]

for i in tqdm(mylist):
    time.sleep(1)

Получаем:



Само собой, в комплекте идёт куча настроек и опций.


Используем alive-progress


Ещё один вариант синтаксиса, побольше дефолтных анимаций, чем в предыдущих примерах:


from alive_progress import alive_bar
import time

mylist = [1,2,3,4,5,6,7,8]

with alive_bar(len(mylist)) as bar:
    for i in mylist:
        bar()
        time.sleep(1)

Результат:



GUI индикатор прогресса для скрипта


Иногда возникает необходимость предоставить конечному пользователю графический индикатор.



Сколько кода нужно, чтобы достигнуть такого результата? Немного:


import PySimpleGUI as sg
import time

mylist = [1,2,3,4,5,6,7,8]

for i, item in enumerate(mylist):
    sg.one_line_progress_meter('This is my progress meter!', i+1, len(mylist), '-key-')
    time.sleep(1)

Индикатор в приложении PySimpleGUI


Рассмотрим реализацию индикатора в PySimpleGUI.



Вот как это сделать:


import PySimpleGUI as sg
import time

mylist = [1,2,3,4,5,6,7,8]

progressbar = [
    [sg.ProgressBar(len(mylist), orientation='h', size=(51, 10), key='progressbar')]
]
outputwin = [
    [sg.Output(size=(78,20))]
]

layout = [
    [sg.Frame('Progress',layout= progressbar)],
    [sg.Frame('Output', layout = outputwin)],
    [sg.Submit('Start'),sg.Cancel()]
]

window = sg.Window('Custom Progress Meter', layout)
progress_bar = window['progressbar']

while True:
    event, values = window.read(timeout=10)
    if event == 'Cancel'  or event is None:
        break
    elif event == 'Start':
        for i,item in enumerate(mylist):
            print(item)
            time.sleep(1)
            progress_bar.UpdateBar(i + 1)

window.close()

Заключение


Как видите, нет ничего сложного в добавлении информации о прогрессе выполнения: кода немного, а отзывчивость повышается очень сильно. Используйте индикаторы, чтобы больше никогда не гадать, завис ли процесс или нет!

Комментарии (10)


  1. shpaker
    10.01.2020 10:57
    +2

    Я бы упомянул ascii-последовательности хотя бы как дань истории :)


    Конечно, прикольно взять готовое и прикрутить в два щелчка… но на самом деле индикатор прогресса можно запилить самому и это достаточно просто.


    Что-то вроде такого у меня сейчас вышло:


    from time import sleep
    from sys import stdout
    
    JUMP_LEFT_SEQ = '\u001b[100D'
    DELAY = 0.05
    
    def loading():
        for i in range(0, 127):
            sleep(DELAY)
            print(JUMP_LEFT_SEQ, end='')
            print(f'Progress: {i:0>3}%', end='')
            stdout.flush()
        print()
    
    loading()


    1. Woodroof
      10.01.2020 16:59

      Ещё можно в стиле ninja build:

      class ProgressBar:
          def __init__(self, total):
              self.total = total
              self.current = 0
              self.prev_len = 0
      
          def next(self, string):
              self._clear()
      
              self.current += 1
              assert self.current <= self.total, 'Incorrect total task count'
      
              current_string = '[{}/{}] {}'.format(self.current, self.total, string)
              self.prev_len = len(current_string)
              print(current_string, end='', flush=True)
      
          def _clear(self):
              import sys
      
              if self.current == 0:
                  return
      
              if sys.__stdout__.isatty():
                  print('\r', end='')
                  print(' ' * self.prev_len, end='')
                  print('\r', end='')
              else:
                  print('')


  1. shpaker
    10.01.2020 10:58

    x


    1. perfect_genius
      10.01.2020 22:28

      у


      1. lolipop
        10.01.2020 23:18

        z
        (combo braker)


  1. Gwynn
    10.01.2020 11:11
    +1

    Спасибо за статью.
    Хорошо бы еще упомянуть, как его не выводить, если скрипт запущен не в интерактивном режиме (например в пайплайне), иначе сильно замусоривается лог выполнения.


    1. myrov_vlad
      10.01.2020 19:15

      По моему опыту это зависит от библиотеки: например в tqdm есть аргумент disable, при значении True которого прогресс бар не рисуется.


  1. deus
    10.01.2020 22:10

    Посмотрите ещё progress из click от Армина.


    with click.progressbar(length=total_size,
                           label='Unzipping archive') as bar:
        for archive in zip_file:
            archive.extract()
            bar.update(archive.size)


  1. ytochka_alisa
    10.01.2020 23:20

    А как же Tkinter?


    1. shpaker
      11.01.2020 21:36

      Как по мне, так какое-то странное извращение — в cli-тулзы тащить гуевые компоненты для индикации прогресса.