見えない処理を見える化:Pythonのtqdmライブラリ活用術

Pythonでプログラムを実行していると、特に長時間かかる処理の場合、「今どこまで進んでいるのか」「あとどれくらいで終わるのか」が分からずイライラした経験はありませんか?私はいつも「イライラ?」というよりも「やきもき?」してます😑

例えば、大量のファイルを処理したり、APIから何件ものデータを取得したりすると、進捗状況が見えません。さらに以下のようなことが気がかりになってきます。

  • プログラムが動いているのか止まっているのか分からない
  • 残り時間が予測できず、次の作業の計画が立てられない
  • 処理速度の問題に気づきにくい

そんな問題を抱えているときには、プログレスバーを使用することで解決できることもあります。そこで今回はプログレスバーを表示できるtqdmライブラリについて実験してみました。

tqdmライブラリの概要

tqdmは「進歩」を意味するアラビア語由来の言葉だそうです(発音は🤔どういうのですかね)。名前の通りPythonプログレスバーを実装できる素晴らしいライブラリです。

tqdmの主な特徴

  • 最小限のコード変更で実装可能
  • コマンドラインGoogle Colabなど様々な環境で動作
  • カスタマイズ性が高く、色や表示形式を変更可能
  • 並列処理にも対応
  • 軽量で依存関係が少ない

最もいいと感じた点は、たった数行の変更でプログレスバーを表示できる点ですね🤩

比較例

# 変更前
for i in range(1000):
    process_item(i)
#
# 変更後
from tqdm import tqdm
for i in tqdm(range(1000)):
    process_item(i)

この簡単な変更だけで、次のような情報を含むプログレスバーが表示できます。

76%|████████████████████████      | 7568/10000 [00:33<00:10, 229.00it/s]

基本的な使い方

インストール方法

tqdmのインストールは、pipコマンドを使ってインストールができます。

$ pip install tqdm

シンプルなループの使用

一番基本的な使い方は、繰り返し処理(ループ)部分にtqdmを適用します。

from tqdm import tqdm
import time

# 基本的な使用例
for i in tqdm(range(100)):
    time.sleep(0.1) # 処理(今回は遅延を入れています)

この例を実行すると、以下のように表示されます。

 45%|██████████████▌         | 45/100 [00:04<00:05, 10.02it/s]

このプログレスバーでは以下のような情報が表示されます。(値は上記の例の場合)

  • 進捗率(パーセンテージ): 45%
  • 視覚的なバー: ██████████████▌
  • 処理済み/全体: 45/100
  • 経過時間: 00:04
  • 残り時間: 00:05
  • 処理速度: 10.02it/s(1秒あたり約10項目)

リスト・イテレータのループでの使用

tqdmはループに使用するiterable(リスト、タプル、ジェネレータなど)でも使用が可能です。

リスト処理の例

# リストでの使用例
my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
for item in tqdm(my_list):
    time.sleep(0.5)  # リストの各項目の処理をいれていくことになります

ファイル処理の例

with open('large_file.txt', 'r') as f:
    for line in tqdm(f, desc="Processing file"):
        time.sleep(0.1) # 各行の処理をいれていくことになります

辞書操作の例

my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
for key, value in tqdm(my_dict.items(), total=len(my_dict)):
    time.sleep(0.2) # 各キー・値ペアの処理をいれていくことになります

このように、ループで処理する部分にtqdm()をつけることで簡単にプログレスバーが使えるようになります。

ちょっとした応用

カスタムパラメータの設定

tqdmは多くのパラメータを持っていて、様々な状況に合わせた設定が可能です。

from tqdm import tqdm
import time

# 様々なパラメータを設定した例
for i in tqdm(range(100),
              desc="Processing",    # 説明テキスト
              leave=False,          # ループ終了後にバーを消す
              ncols=75,             # バーの幅を指定
              unit="item",          # 単位の表示
              colour="green",       # バーの色
              smoothing=0.3):       # 残り時間計算の平滑化係数
    time.sleep(0.05)

主なパラメータ一覧

パラメータ 説明
desc バーの前に表示する説明テキスト
total 処理する総数(イテラブルから自動判定できない場合に指定)
leave Trueの場合、ループ完了後もバーを残す(デフォルト)。Falseなら消える
ncols バー全体の幅(文字数)
unit 処理単位('it'、'MB'、'file'など)
disable Trueにするとプログレスバーを無効化(ログ出力時などに便利)
position 複数バー表示時の位置指定
initial 初期カウント値
colour バーの色

回数が不定なループでの使用法

これまでの例では、ループの回数はリストなど回数があらかじめ分かるものでした。では、処理回数がわからない場合にはどうしたらいいでしょうか🤔 処理回数が事前にわからない場合でも、以下のようにpbar.update()を使って手動で進捗を更新することで、tqdmを効果的に使用できます。

from tqdm import tqdm
import time
import random

# 総数が不明な場合
with tqdm(total=None) as pbar:
    count = 0
    # 終了条件がランダムなループをシミュレート
    while count < random.randint(50, 150):
        time.sleep(0.1)
        count += 1
        pbar.update(1)  # 手動で進捗更新
        pbar.set_description(f"Processed {count} items")

Processed 78 items: 78it [00:07, 10.23it/s]

この処理ではプログレスバーpbarの値をループ内でnの値(処理数)をループ判定時に毎回更新しています。今回はパーセンテージは表示されていませんが、処理数とおおよその処理速度を確認が可能です。

動的にループ回数の更新方法

処理中に総数が変わる場合(例:ファイル探索で新しいファイルが見つかたなど)は、totalを動的に更新して対応が可能です。

以下の例では、途中で40回目の処理で、ループ回数であるtotalが50から100に変わることで、プログレスバーの表示が自動的に調整されています。

from tqdm import tqdm
import time
import random

estimated_items = 50  # 最初の予測値
with tqdm(total=estimated_items) as pbar:
    processed = 0
    
    while processed < 100:  # 実際は100まで処理する
        # 40回処理した段階で処理数を100回へ値を変更
        if processed == 40:
            new_total = 100
            # 総数を更新
            pbar.total = new_total
            pbar.refresh()  # 表示を更新
        
        time.sleep(0.1)
        processed += 1
        pbar.update(1)

ループ回数が50回から

ループ回数が100回に増えています

表示のカスタマイズ

色の変更方法

tqdmは、プログレスバーの色をカスタマイズできます。これにより、複数の処理を視覚的に区別したり、緊急度によって色分けしたりできます。

from tqdm import tqdm
import time

# 基本的な色指定
colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'white']

for color in colors:
    for i in tqdm(range(100), 
                 desc=f"{color.capitalize()} bar", 
                 colour=color):
        time.sleep(0.01)

16進数のカラーコードを使用してより細かく制御することも可能です。以下の例では鮮やかなオレンジ色のプログレスバーが表示されます。

from tqdm import tqdm
import time

for i in tqdm(range(100), 
             desc="Custom color", 
             colour="#ff9900"):  # オレンジ色(16進カラーコード)
    time.sleep(0.01)

進捗に応じて色を変える場合には以下のようにも出来ます。

from tqdm import tqdm
import time

class ColorProgressBar(tqdm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
    def update(self, n=1):
        super().update(n)
        # 進捗状況に基づいて色を変更
        if self.n < self.total * 0.3:
            self.colour = 'red'  # 30%未満は赤
        elif self.n < self.total * 0.7:
            self.colour = 'yellow'  # 30%~70%は黄色
        else:
            self.colour = 'green'  # 70%以上は緑

# 動的に色が変わるプログレスバーを使用
with ColorProgressBar(total=100, desc="Progress") as pbar:
    for i in range(100):
        time.sleep(0.05)
        pbar.update(1)

表示フォーマットのカスタマイズ

tqdmではbar_formatパラメータを使ってプログレスバー以外の表示形式も細かく制御できます。

from tqdm import tqdm
import time

# カスタムフォーマット
custom_format = "{desc}: |{bar}| {percentage:3.0f}% [{elapsed}<{remaining}]"
for i in tqdm(range(100), 
             desc="Loading", 
             bar_format=custom_format):
    time.sleep(0.05)

表示例

Loading: |███████████████▌                   | 46% [00:02<00:03]

利用可能なフォーマット指定

プレースホルダ 説明
{desc} 説明テキスト
{bar} プログレスバー本体
{percentage:3.0f} パーセンテージ(3桁、小数点なし)
{n_fmt}/{total_fmt} 現在値/合計
{elapsed} 経過時間
{remaining} 残り時間(推定)
{rate_fmt} 処理速度
{postfix} 追加情報

高度なカスタマイズ例

from tqdm import tqdm
import time

# 詳細なカスタムフォーマット
format_detailed = "{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} " \
                  "[{elapsed}<{remaining}, {rate_fmt}{postfix}]"
                  
for i in tqdm(range(1000), 
             desc="Processing", 
             bar_format=format_detailed,
             ncols=80):
    time.sleep(0.01)

説明・後置テキストの追加

処理の詳細情報を動的に表示したい場合は、set_description()set_postfix()メソッドを使用します。

例1

from tqdm import tqdm
import time
import random

with tqdm(total=100) as pbar:
    for i in range(100):
        time.sleep(0.05)
        
        # 処理中のファイル名を表示(バーの前に表示)
        current_file = f"file_{i:03d}.txt"
        pbar.set_description(f"Processing {current_file}")
        
        # 詳細情報を表示(バーの後ろに表示)
        size = random.randint(10, 1000)
        status = random.choice(['OK', 'WARNING', 'ERROR'])
        pbar.set_postfix(filesize=f"{size}KB", status=status)
        
        pbar.update(1)

表示

Processing file_042.txt: 42%|██████▌    | 42/100 [00:02<00:03, 18.42it/s, filesize=763KB, status=OK]

例2

from tqdm import tqdm
import time
import random

with tqdm(total=100) as pbar:
    total_size = 0
    errors = 0
    
    for i in range(100):
        time.sleep(0.05)
        
        # 処理状況をシミュレート
        chunk_size = random.randint(5, 20)
        total_size += chunk_size
        if random.random() < 0.1:  # 10%の確率でエラー発生
            errors += 1
            
        # 現在の統計情報を更新
        pbar.set_postfix(
            processed=f"{total_size}KB", 
            errors=errors,
            avg_speed=f"{total_size/(i+1):.1f}KB/it"
        )
        pbar.update(1)

表示

100%|████████████████████████████████| 100/100 [00:05<00:00, 19.41it/s, avg_speed=13.1KB/it, errors=8, processed=1314KB]

これらの機能を組み合わせることで、進捗表示だけでなく、処理の情報も表示できます。

ログ出力との共存

スクロールを防ぐ方法

プログレスバーを使用中に通常のprint文を使うと、プログレスバーがスクロールしてしまい表示が乱れてしまいます。tqdmではこの問題にも対応しています。

方法1 tqdm.write()を使用する

方法その1はprint()ではなく、tqdm.write()を使用することです。

from tqdm import tqdm
import time

for i in tqdm(range(100)):
    time.sleep(0.1)
    if i % 10 == 0:
        # プログレスバーを乱さずにメッセージを出力
        tqdm.write(f"Reached milestone: {i}")

tqdm.write()は内部でプログレスバーを適切に処理し、出力後にプログレスバーを正しい位置に再描画します。これにより、メッセージとプログレスバーが混在しても表示が乱れることはありません。

個人的には以下2つの方法のほうがおすすめです🙂

方法2 説明テキスト部分の更新

2つ目の方法は、descriptionを使用することです。頻繁に変わらない情報ならここに表示することで表示をスッキリさせる事ができます。

from tqdm import tqdm
import time
import random

pbar = tqdm(range(100))
for i in pbar:
    time.sleep(0.1)
    
    # 現在の状態をプログレスバーの説明部分に表示
    if i % 10 == 0:
        status = random.choice(['読み込み中', '解析中', '変換中', '保存中'])
        pbar.set_description(f"状態: {status}")

方法3 後置情報の更新

3つ目の方法はset_postfixを使用することです。詳細な情報や統計データを表示する場合は表示がスッキリします。

from tqdm import tqdm
import time
import random

with tqdm(total=100) as pbar:
    success = 0
    error = 0
    
    for i in range(100):
        time.sleep(0.1)
        
        # 処理結果をシミュレート
        if random.random() < 0.8:  # 80%の確率で成功
            success += 1
        else:
            error += 1
            
        # 統計情報を更新
        pbar.set_postfix(成功=success, エラー=error, 成功率=f"{success/(i+1):.1%}")
        pbar.update(1)

表示例

 63%|██████████████████▋          | 63/100 [00:06<00:03, 9.85it/s, 成功=51, エラー=12, 成功率=81.0%]

一時的にプログレスバーを非表示にする

重要なメッセージを出力する場合は、一時的にプログレスバーをクリアして出力した後、再表示する方法もあります:

from tqdm import tqdm
import time

pbar = tqdm(range(100))
for i in pbar:
    time.sleep(0.1)
    
    if i == 50:
        # 一時的にプログレスバーをクリア
        pbar.clear()
        
        # 重要なメッセージを出力
        print("=" * 50)
        print("処理の半分が完了しました!")
        print("ここまでの統計情報:")
        print("- 平均処理時間: 0.1秒/項目")
        print("- 推定残り時間: 5秒")
        print("=" * 50)
        
        # プログレスバーを再表示
        pbar.refresh()

注意 プログレスバーの表示先

コマンドライン標準出力(stdout)にリダイレクト(>など)を使用すると、リダイレクトの結果にはプログレスバーは出力されません。これはプログレスバー標準エラー出力に表示されるためです。最初は「バーが表示されない」と焦ってしまうかもしれませんが(私は焦りました🥲)、理解しておくと安心です。

実践的なユースケース

自分が最近使用しているのは以下のようなパターンとなります。

ファイル処理での活用例

大量のファイルを処理する場合、tqdmを使って進捗状況を可視化できます。

from tqdm import tqdm
import os
import time

def process_files(directory):
    # ディレクトリ内のすべてのファイルパスを取得
    file_list = []
    for root, _, files in os.walk(directory):
        for file in files:
            file_list.append(os.path.join(root, file))
    
    processed_sizes = 0
    failed_files = 0
    
    # tqdmでファイル処理の進捗を表示
    for file_path in tqdm(file_list, desc="ファイル処理中", unit="ファイル"):
        try:
            # ファイルサイズを取得
            file_size = os.path.getsize(file_path)
            
            # ファイル処理をシミュレート
            time.sleep(0.05)
            
            processed_sizes += file_size
            
        except Exception as e:
            failed_files += 1
            tqdm.write(f"エラー: {file_path} - {str(e)}")
    
    return {
        "処理ファイル数": len(file_list),
        "処理失敗": failed_files,
        "合計サイズ": f"{processed_sizes / (1024*1024):.2f} MB"
    }

# 使用例
results = process_files("/path/to/documents") #ファイルの格納されたディレクトリを指定
print(f"処理結果: {results}")

Google Colabでの使用法

Google Colabtqdmを使用する場合、特にノートブック用のバージョンであるtqdm.notebookを使うのがおすすめです。

ライブラリのインストールは、その他の処理系とは異なることなく以下のように行います。

!pip install tqdm
# Google Colabでのtqdmの使用例
from tqdm.notebook import tqdm as tqdm_notebook
import time

# 基本的な使用法(HTMLベースの表示になります)
for i in tqdm_notebook(range(100)):
    time.sleep(0.1)

長時間実行される処理の例

from tqdm.notebook import tqdm as tqdm_notebook
import numpy as np
import time

# 機械学習のエポック訓練をシミュレート
def train_model(epochs, batch_size, train_data):
    history = []
    
    # エポックのプログレスバー
    with tqdm_notebook(total=epochs, desc="訓練") as epoch_pbar:
        for epoch in range(epochs):
            losses = []
            
            # バッチ処理のプログレスバー
            n_batches = len(train_data) // batch_size
            with tqdm_notebook(total=n_batches, desc=f"エポック {epoch+1}/{epochs}", leave=False) as batch_pbar:
                for batch_idx in range(n_batches):
                    # バッチ訓練をシミュレート
                    time.sleep(0.05)
                    loss = 1 - 0.8 * (epoch / epochs) - 0.2 * np.random.random()
                    losses.append(loss)
                    
                    # バッチごとの情報更新
                    batch_pbar.set_postfix(loss=f"{loss:.4f}")
                    batch_pbar.update(1)
            
            avg_loss = np.mean(losses)
            history.append(avg_loss)
            
            # エポックごとの情報更新
            epoch_pbar.set_postfix(loss=f"{avg_loss:.4f}")
            epoch_pbar.update(1)
    
    return history

# 訓練データのシミュレーション(実際はデータセットが入ります)
fake_data = [i for i in range(1000)]
history = train_model(epochs=10, batch_size=32, train_data=fake_data)

Google Colabでは、プログレスバーがHTML形式で表示され、かなり視覚的になります。

おわりに

tqdmはとても便利なライブラリで、長時間実行されるプログラムの可視化が容易になります。少しのコード追加で、プログラムの体験が劇的に変わります🤩

データ処理やAPIからの大量データ取得など、「終わるまで待つしかない」と諦めていた作業が、tqdmを導入することで「あとどれくらいで終わるな」と予測できるようになり、計画が立てやすくなりました。また、基本的な使い方はtqdm(iterable)と一行で実装できる点が気に入っています。加えて、Google Colabでもほぼ同様に使用できるのもいいですね。