Вы когда-нибудь оказывались в ситуации, когда скрипт на Python выполняется очень долго и вы задаетесь вопросом, происходит ли вообще что-нибудь за экраном?
Такая неопределенность может побудить вас прервать почти завершенное выполнение или бесконечно ждать окончания работы скрипта, хотя его работа уже прервалась.
Библиотека tqdm решает эту проблему, предоставляя индикаторы выполнения для ваших скриптов.
Содержание
- Что собой представляет tqdm?
- Как установить tqdm
- Простой пример использования tqdm
- Кастомизация индикаторов tqdm
- Работа с более сложными сценариями
- Распространенные проблемы при использовании tqdm
- Заключение
Что собой представляет tqdm?
Tqdm — это библиотека Python, которая предоставляет быстрые, расширяемые индикаторы выполнения для циклов и итерируемых объектов. Такие индикаторы позволяют отслеживать продвижение задач, требующих много времени.
Название библиотеки означает «прогресс» на арабском (taqadum, تقدّم) и является сокращением от «я тебя очень люблю» на испанском (te quiero demasiado).
Tqdm отслеживает прогресс и обновляет индикатор выполнения, подсчитывая количество итераций, вычисляя прошедшее и оставшееся время, а также визуализируя общий прогресс путем заполнения полосы.
Библиотека использует интеллектуальные алгоритмы для прогнозирования оставшегося времени и пропускает ненужные отображения итераций, чтобы минимизировать накладные расходы.
Использование tqdm дает ряд преимуществ:
- Визуальная обратная связь. Прогресс-бары позволяют пользователям видеть, какая часть задачи выполнена, и оценить, сколько времени может занять оставшаяся часть.
- Кроссплатформенность. Библиотека tqdm работает на любой платформе (Linux, Windows, Mac, FreeBSD, NetBSD, SunOS), в любой консоли или в графическом интерфейсе.
- Простая интеграция. Tqdm легко интегрируется с блокнотами Jupyter, распространенными библиотеками, такими как Pandas, и общими конструкциями Python, такими как циклы.
- Кастомизация. Предлагается несколько вариантов настройки внешнего вида и поведения прогресс-баров, о которых мы расскажем позже.
- Производительность. В то время как аналогичные пакеты, такие как ProgressBar, имеют накладные расходы 800 нс/итерацию, tdqm при накладных расходах в 60 нс/итерацию работает гораздо быстрее.
Как установить tqdm
Как и для большинства библиотек Python, самый простой способ установить tqdm — использовать менеджер пакетов pip.
pip install tqdm
Простой пример использования tqdm
Чтобы создать индикатор выполнения, мы оборачиваем наш итерируемый объект в функцию tqdm()
, которую импортируем из модуля tqdm.
Давайте рассмотрим простой пример. Функция time.sleep(0.01)
служит плейсхолдером для кода, который должен выполняться в каждой итерации.
from tqdm import tqdm for i in tqdm(range(1000)): time.sleep(0.01) # …
В качестве сокращения для tdqm(range(n))
можно также использовать trange(n)
. Следующий код вернет точно такой же результат, как и предыдущий:
from tqdm import tqdm for i in trange(1000): time.sleep(0.01) # …
Давайте присмотримся к индикатору выполнения, чтобы узнать, какую информацию он предоставляет:
- Индикаторы выполнения:
- Процент итераций
- Заполнение индикатора
- Доля от общего числа итераций
- Показатели:
- Истекшее время
- Предполагаемое оставшееся время
- Производительность (в итерациях в секунду)
Кастомизация индикаторов tqdm
Tqdm предлагает различные возможности настройки внешнего вида и поведения индикаторов выполнения. Мы рассмотрим наиболее важные параметры, а затем изучим более сложные варианты использования tqdm.
Добавление описания к индикатору выполнения
Одна из распространенных настроек — добавление описательной метки с помощью параметра desc
. Описание делает индикатор выполнения более информативным и помогает следить за разными итерируемыми объектами.
Например, добавление параметра desc=”Processing large range”
выведет заголовок «Processing large range» слева от индикатора выполнения.
for _ in tqdm(range(20000000), desc="Processing large range"): continue
Попробуйте запустить приведенный выше код в своей среде с параметром desc
и без него и обратите внимание на разницу.
Указание общего количества итераций
Параметр total
задает общее количество итераций в цикле, что позволяет tqdm предоставлять более точные оценки оставшегося времени и процента завершения.
В базовом примере с использованием tqdm(range())
или trange()
в этом нет необходимости, поскольку количество итераций уже заключено в скобки. Однако есть два основных сценария, в которых добавление параметра total=n
(где n
— общее число) может быть полезным:
1. Итерируемые объекты без метода len()
Для итерируемых объектов без метода len(), например генераторов с неизвестным количеством элементов, необходимо указать значение total
вручную. Без этого параметра tqdm()
покажет только количество завершенных итераций. Обратите внимание, что если фактическое количество элементов превышает указанное значение total
, индикатор выполнения перезапускается.
from tqdm import tqdm import time import random # Function generating random number between 1 and 100 def generate_random_numbers(): while True: yield random.randint(1, 100) # Without total: tqdm() only shows number of iterations, does not know total for num in tqdm(generate_random_numbers(), desc=’Random Iteration’): time.sleep(0.01) # …
# With total (assuming you know the desired number of iterations): tqdm() shows progress num_iterations = 1000 for num in tqdm(generate_random_numbers(), total=num_iterations, desc="Random Iteration"): time.sleep(0.01) # …
2. Ручное обновление
Если мы обновляем индикатор выполнения внутри функции вручную с помощью метода update()
, то для корректного отслеживания выполнения необходимо указать значение total
. Метод update()
позволяет, например, реагировать на динамически меняющиеся процессы или реализовать пользовательское отслеживание прогресса. К этому мы вернемся позже.
Настройка внешнего вида
Чтобы изменить порядок размещения элементов индикатора, можно установить предпочтительный формат строки в качестве значения для параметра bar_format
. Таким образом можно управлять размещением различных элементов, таких как процентное отношение, прошедшее время и символ заполнения полосы. За более подробной информацией вы можете обратиться к документации.
Еще одна настройка внешнего вида может быть выполнена с помощью параметра colour
. Можно использовать как слова, например «green», так и шестнадцатеричный код в строковом виде, например «#00ff00».
Параметр leave
определяет, останется ли полоса прогресса видимой после завершения работы. Если установить значение True, то индикатор сохранится после завершения цикла, а если установить значение False, то он исчезнет.
Давайте рассмотрим визуальные различия на примере. Следующий код создает три индикатора выполнения: один с настройками по умолчанию, во втором изменены порядок элементов и цвет, а третий настроен на исчезновение после завершения. Результат можно видеть в GIF ниже.
from tqdm import tqdm import time # Progress bar 1: Default settings for i in tqdm(range(300)): time.sleep(0.01) # Progress bar 2: Customized bar format and color for i in tqdm(range(300), bar_format='[{elapsed}<{remaining}] {n_fmt}/{total_fmt} | {l_bar}{bar} {rate_fmt}{postfix}', colour='yellow'): time.sleep(0.01) # Progress bar 3: Customized bar format and color, leave=False for i in tqdm(range(300), bar_format='[{elapsed}<{remaining}] {n_fmt}/{total_fmt} | {l_bar}{bar} {rate_fmt}{postfix}', colour='red', leave=False): time.sleep(0.01)
Работа с более сложными сценариями
Теперь, когда мы знаем, как создать и настроить простой индикатор выполнения, можно перейти к более сложным случаям.
Вложенные индикаторы выполнения
Вложенные циклы — это циклы внутри других циклов. Соответственно, вложенные индикаторы выполнения — это прогресс-бары для циклов, содержащихся внутри других циклов. Чтобы создать их, оберните каждый цикл функцией tqdm()
и добавьте описательные метки для каждого итерируемого объекта.
Следующий код содержит три вложенных цикла и может служить примером того, как будут выглядеть вложенные индикаторы выполнения:
from tqdm import trange import time for i in trange(3, desc='outer loop'): for j in trange(2, desc='middle loop'): for k in trange(6, desc='inner loop'): time.sleep(0.01)
В итоговом результате можно заметить закономерность в обновлении индикаторов выполнения. Tqdm всегда будет начинать с самого внешнего цикла и идти до самого внутреннего.
Предположим, что у нас есть три вложенных цикла, как в примере. После начальной внутренней итерации tqdm перейдет к среднему циклу, обновит его как завершивший одну итерацию, а затем вернется к внутреннему итерируемому циклу. Этот процесс повторяется до тех пор, пока средний цикл не будет отмечен как завершенный. После этого в индикаторе внешнего цикла появится одна завершенная итерация.
То же самое происходит между внешним и средним циклом. В итоге последний внутренний цикл завершает среднюю итерацию, которая, в свою очередь, завершает последнюю внешнюю итерацию.
Ручное обновление
Обновление индикаторов выполнения вручную может быть полезно в нескольких сценариях:
- Итерируемые объекты с неизвестной длиной. Когда мы работаем с итерируемыми объектами с неопределенной длиной (например, с генераторами или сетевыми потоками), мы можем вручную обновлять индикатор выполнения на основе объема обработанных данных или количества выполненных операций.
- Динамически изменяющиеся процессы. Если количество итераций или время обработки одной итерации может меняться в процессе выполнения, ручное обновление позволит настроить индикатор выполнения соответствующим образом.
- Пользовательское отслеживание прогресса. Для большего контроля над индикатором выполнения можно обновлять его вручную на основе определенных критериев или событий. Например, можно обновлять шкалу прогресса на основе завершения определенных этапов или на основе хода выполнения отдельных задач в рамках большого процесса.
- Интеграция с внешними системами. Если мы интегрируем tqdm с внешними системами или библиотеками, которые не предоставляют естественного способа отслеживания прогресса, можно использовать ручное обновление для синхронизации индикатора с внешним процессом.
Для ручного обновления индикатора выполнения tqdm важно указать параметр total как оценку максимального ожидаемого количества итераций. Затем, по мере обработки новых элементов, нужно обновлять индикатор выполнения с помощью метода update()
. Значение update
должно представлять количество итераций, обработанных с момента последнего обновления.
Допустим, мы ожидаем, что наш итерируемый объект будет содержать до 750 элементов. В данном примере фактическая длина — это случайное число между 100 и 1000, которое нам неизвестно. Мы инициируем progress_bar
, устанавливая estimated_total
равным 750. Затем мы итерируем данные, обновляя индикатор выполнения после обработки каждой точки.
from tqdm import tqdm def process_data(data): time.sleep(0.01) # simulate processing data processed_data = data return processed_data # Generate an iterable with random length between 100 and 1,000 random_length = random.randint(100, 1000) data_list = [i for i in range(random_length)] # Define estimated maximum number of iterations estimated_total = 750 # Define the progress bar using the estimated_total progress_bar = tqdm(total=estimated_total) # Iterating through data list of unknown length for data in data_list: processed_data = process_data(data) progress_bar.update(1)
Процессный параллелизм
Параллелизм и многопоточность — это методы, используемые для одновременного выполнения задач и повышающие производительность и скорость отклика. В таких сценариях бывает сложно отследить ход выполнения отдельных задач или общий ход параллельного выполнения. Tqdm может стать ценным инструментом для обеспечения визуальной обратной связи и контроля за ходом выполнения параллельных операций.
Модуль tqdm.contrib.concurrent предоставляет специализированные функции для создания индикаторов выполнения в контекстах многопроцессной или многопоточной обработки. Эти функции управляют синхронизацией и взаимодействием между главным процессом и рабочими процессами или потоками, обеспечивая корректное обновление индикатора выполнения. Они предназначены для работы с concurrent.futures
API с использованием функции ProcessPoolExecutor()
или ThreadPoolExecutor()
.
Вот пример использования модуля tqdm.contrib.concurrent.futures:
import concurrent.futures from tqdm.contrib.concurrent import process_map def process_data(data): for i in tqdm(range(100), desc=f"Processing {data['name']}"): # Process data time.sleep(0.01) if __name__ == '__main__': with concurrent.futures.ProcessPoolExecutor() as executor: results = process_map(process_data, [ {'name': 'dataset1'}, {'name': 'dataset2'}, # … ])
В этом примере функция process_data()
содержит прогресс-бар tqdm для отслеживания хода выполнения. Функция process_data()
будет выполняться параллельно для каждого элемента данных в списке. Это означает, что одновременно будет отображаться несколько индикаторов, каждый из которых будет представлять ход выполнения отдельного процесса. Параметр desc
задается для динамического создания описания для каждого индикатора выполнения на основе имени соответствующего набора данных, что поможет различать разные индикаторы.
Интеграция с pandas
Модуль tqdm.pandas предоставляет удобный способ добавления индикаторов выполнения к операциям pandas. Это особенно полезно для трудоемких операций над большими датафреймами, так как обеспечивает визуальную обратную связь о ходе выполнения задачи. Мы можем применить декоратор tqdm.pandas()
к любой функции pandas, работающей со строками или столбцами.
Для начала мы определим случайный DataFrame со 100 000 строк и вызовем декоратор tqdm.pandas()
. Если мы хотим настроить индикатор выполнения, сейчас самое время это сделать, поскольку функции progress_apply()
и progress_map()
не принимают параметры tqdm()
. Здесь мы хотим дать имена индикаторам выполнения, поэтому указываем параметр desc
.
import pandas as pd import numpy as np from tqdm import tqdm df = pd.DataFrame(np.random.randint(0, 10, (100000, 6))) tqdm.pandas(desc='DataFrame Operation')
Теперь мы можем применять функции к строкам, столбцам или всему DataFrame. Вместо того чтобы использовать функцию apply()
или map()
, вызовите progress_apply()
или progress_map()
, и индикатор выполнения будет отображен. Помните, что apply()
и progress_apply()
можно применять к DataFrame, строкам или столбцам, а map()
и progress_map()
— только к Series или столбцам. Например:
# Halving each value in the DataFrame using progress_apply() Result_apply = df.progress_apply(lambda x: x / 2) # Doubling each element of the first column using progress_map() result_map = df[0].progress_map(lambda x: x * 2)
Распространенные проблемы при использовании tqdm
Давайте обсудим некоторые распространенные проблемы, возникающие при использовании tqdm, и способы их устранения.
Индикатор выполнения не обновляется
Одна из самых распространенных проблем — не обновляющийся индикатор выполнения. Это часто происходит из-за проблем с буферизацией, особенно в таких средах, как Jupyter Notebooks. Когда вывод буферизируется, индикатор выполнения может не отображаться или немедленно обновляться, что приводит к ощущению замороженного или неотвечающего процесса.
Использование модуля tqdm.notebook позволяет решить проблемы с буферизацией и обеспечить корректное обновление индикатора в Jupyter Notebooks. Этот модуль предоставляет графический индикатор выполнения, специально разработанный для сред Jupyter. Кроме того, он предлагает удобные для пользователя цветовые подсказки (синий: нормально, зеленый: завершено, красный: ошибка/прерывание).
Если мы прервем код из нашего примера с вложенным индикатором выполнения, то он будет выглядеть следующим образом:
Еще один эффективный способ устранения неполадок с необновляющимися индикаторами выполнения — явный сброс (англ. flush) потока вывода. Когда данные записываются в стандартный поток вывода (например, с помощью функции print()
), они обычно буферизируются перед отправкой на устройство вывода. Сброс потока вывода заставляет интерпретатор Python немедленно отправить все буферизованные данные на устройство вывода, гарантируя, что данные будут отображены или записаны без задержки.
Чтобы сбросить вывод, используйте метод flush()
стандартного потока вывода. Для более отзывчивого вывода рассмотрите возможность сбрасывать поток вывода почаще, возможно, после нескольких итераций или через определенное время. Но не забывайте, что это может привести к дополнительным накладным расходам, так что нужно искать компромисс.
Вот один из примеров применения этого метода в простом процессе tqdm:
import sys import time from tqdm import tqdm for i in tqdm(range(100)): time.sleep(0.1) sys.stdout.flush() # Flush the output stream after each iteration
Проблемы совместимости
Хотя tqdm в целом совместима с большинством сред и библиотек Python, иногда могут возникать проблемы с совместимостью или неожиданное поведение. К числу распространенных сценариев, о которых следует знать, относятся:
- Пользовательские потоки вывода. При использовании пользовательских потоков вывода или перенаправлении вывода в файлы tqdm может работать не так, как ожидалось. Необходимо убедиться, что используемый поток вывода поддерживает операции, необходимые для отображения индикатора выполнения.
- Сторонние библиотеки. В некоторых случаях tqdm может неожиданно взаимодействовать со сторонними библиотеками, особенно с теми, которые сами обрабатывают вывод или отслеживают прогресс. Можно попробовать отключить или изменить соответствующие функции сторонних библиотек и посмотреть, решит ли это проблему.
- Совместимость версий. Хорошая практика — использовать совместимые версии tqdm и других библиотек. Проверьте документацию библиотеки на предмет известных проблем совместимости с конкретными версиями Python или другими зависимостями.
При возникновении проблем с совместимостью можно воспользоваться следующими обходными путями:
- Даунгрейд или апгрейд. Можно попробовать другую версию tqdm.
- Изменение кода. При необходимости можно внести изменения в наш код, чтобы обойти любые конфликты совместимости.
- Обращение за помощью к сообществу. Если все это не поможет, можно обратиться к сообществу tqdm или на онлайн-форумы за помощью и потенциальными решениями.
Имея в виду эти потенциальные проблемы совместимости и пути их решения (обхода), мы сможем эффективно устранять любые неурядицы, возникающие при использовании tqdm в наших Python-проектах.
Заключение
В заключение можно сказать, что tqdm — это библиотека Python, которая предоставляет нам индикаторы выполнения и полезную статистику, облегчая мониторинг и управление выполнением кода.
Если вы выполняете итерации над большими наборами данных, обучаете модели машинного обучения или выполняете любую другую трудоемкую операцию, tqdm предлагает простой, но мощный способ отслеживать прогресс и быть в курсе состояния вашего кода.
Для дальнейшего изучения не стесняйтесь ознакомиться с документацией библиотеки tqdm и ее исходным кодом и Readme на GitHub.
Перевод статьи “Tqdm Python: A Guide With Practical Examples”.