書いたスクリプトを実行して、画面が黙りこんだまま数分。あの時間、落ち着かない気持ちになりませんか。
動いているのか、それとももう固まっているのか。確かめる方法がないまま、ただ待つしかありません。
その不安をほとんど消してくれるのが、今回紹介するtqdmというライブラリです。ループを包むだけで、進捗バーが出ます。
読み終わるころには、お手元のスクリプトに2行足すだけで使えるようになっているはずです。
進捗が見えないと、処理は途中で止められる¶
先に、なぜ進捗バーを出すのかという話をさせてください。
バーを付けても処理結果は変わりませんし、速くもなりません。技術的には何も得をしていないように見えます。
それでも私は、数分かかるスクリプトには必ず進捗を出すようにしています。理由は単純で、無反応の画面を見た人はそれを止めてしまうからです。
私は10年ほどエンジニアとして開発に関わってきましたが、これで一度痛い目にあいました。数万件のデータ移行スクリプトを同僚に渡したところ、5分ほど何も出てこないのを見てCtrl+Cで中断されてしまったのです。
スクリプトは正常に動いていました。ただ、動いている証拠を何ひとつ出していませんでした。
進捗バーは飾りではありません。処理が生きていることを伝えるための、いちばん安い手段です。
tqdmは、ループを包むだけで動く¶
tqdmはPythonに最初から入っているものではないので、まずインストールから始めます。
pip install tqdm
追加で必要になるライブラリはありません。tqdmは他のパッケージに依存していないので、入れたことで環境が複雑になる心配は小さめです。
使い方は、繰り返したいものをtqdm()で包むだけです。それ以外のコードは一切変えません。
import time
from tqdm import tqdm
names = ['山田', '佐藤', '鈴木', '田中', '高橋']
for name in tqdm(names, desc='処理中'):
time.sleep(0.5) # ここで実際の処理をする
手元のPython 3.11で実行すると、次のような行が更新されながら表示されます。
処理中: 100%|██████████| 5/5 [00:02<00:00, 2.00it/s]
descは、バーの左に出すラベルです。何の処理なのかがひと目でわかるので、付けておくと後々の自分が助かります。
なおtime.sleepの部分は、あくまで重い処理の代わりです。待ち時間の作り方そのものが気になった方は、こちらもあわせてどうぞ。【関連記事】Pythonのtimeモジュールとは?sleepで待つ・処理時間を測る基本を初心者向けに解説
バーに並ぶ数字を読めるようになる¶
出てきた行には、意外といろいろな情報が詰まっています。途中経過はこんな見た目です。
送信中: 60%|██████ | 3/5 [00:00<00:00, 3.33it/s]
それぞれが何を指しているのかを、表にまとめておきます。
| 表示 | 意味 |
|---|---|
送信中 |
descで指定したラベル |
60% |
全体のうち終わった割合 |
██████ |
割合をそのまま絵にしたバー |
3/5 |
終わった数 ÷ 全体の数 |
[00:00<00:00] |
左が経過時間、右が残り時間の予測 |
3.33it/s |
1秒あたりに何回ループが進んだか |
慣れてくると、it/sの数字をいちばん見るようになります。処理が急に遅くなった瞬間が、そこに表れるからです。
残り時間はどうやって出しているのか¶
残り時間は、未来を予知しているわけではありません。ここまでの速度から、残りの回数ぶんを単純に見積もっているだけです。
だから前半が軽くて後半が重い処理では、予測が大きく外れます。私はこの数字を、正確な終了時刻ではなく桁の目安として見ています。
数十秒なのか数十分なのか。それがわかるだけで、待つか別の作業に移るかを判断できます。
総数がわからないとパーセントは出ない¶
ここで、初めて使う方がつまずきやすい場面を見ておきます。
リストのように長さが決まっているものなら、tqdmは自動で全体の数を知ることができます。けれどジェネレータのように、最後まで進まないと個数がわからないものもあります。
そういう相手を包むと、表示がこう変わります。
def read_users():
for i in range(5):
yield i
for user in tqdm(read_users(), desc='集計中'):
time.sleep(0.5)
集計中: 5it [00:02, 2.00it/s]
パーセントもバーも消えて、処理した数と速度だけになりました。全体が何個あるか知らないのだから、割合を出しようがありません。
このときは、totalで全体の数を自分で教えてあげます。
for user in tqdm(read_users(), total=5, desc='集計中'):
time.sleep(0.5)
集計中: 100%|██████████| 5/5 [00:02<00:00, 2.00it/s]
バーが戻ってきました。件数が事前にわかるなら、先にデータベースへ件数だけ問い合わせてtotalに渡すという手も使えます。
ジェネレータそのものの考え方があいまいな方は、先にこちらを読んでおくと納得しやすいはずです。【関連記事】ジェネレータ(yield)っていつ使うの?巨大なデータをメモリ節約して扱う方法
printを混ぜるとバーが崩れる¶
進捗バーを入れたあと、ループの中にprintが残っていると表示が壊れます。
理由は、tqdmが同じ行を書き換え続けることでバーを動かしているからです。そこへ別の出力が割り込むと、書き換えの位置がずれてバーが何行にも散らばります。
解決策はあっけないほど簡単で、printの代わりにtqdm.write()を使います。
for i in tqdm(range(5), desc='送信中'):
time.sleep(0.3)
if i == 2:
tqdm.write(f'{i}番目は宛先が空だったので飛ばしました')
メッセージはバーの上に流れていき、バーは画面の下に残ったままになります。メッセージと進捗が場所を奪い合わないので、どちらも読めます。
ちなみにtqdmのバーは、既定では標準エラー出力へ書き出されています。printの行き先である標準出力とは別なので、結果をファイルへリダイレクトしてもバーは混ざりません。
もっとも、記録として残したい情報ならprintではなくログに出すのが本筋です。その作法はこちらでまとめています。【関連記事】Pythonのロギング(logging)入門。print卒業!プロが使うログ出力の正しい作法
ループ以外の進捗にも使える¶
tqdmはfor文専用の道具だと思われがちですが、そうではありません。
自分で数字を進める書き方¶
ファイルのダウンロードのように、1回で進む量がまちまちな処理もあります。その場合はtotalだけ先に伝えて、進んだぶんを自分で足していきます。
with tqdm(total=100, unit='B', unit_scale=True) as pbar:
for chunk in range(10):
pbar.update(10) # 10ずつ進んだことを伝える
unitは数える単位の名前で、unit_scaleを有効にすると大きな数をキロやメガに直して見せてくれます。ダウンロード量を表示したいときに便利です。
pandasのapplyに進捗を付ける¶
表データを1行ずつ加工する処理も、待ち時間が読めない代表格です。tqdmはpandas向けの入口を用意しています。
from tqdm import tqdm
tqdm.pandas(desc='加工中')
df['result'] = df['price'].progress_apply(heavy_function)
tqdm.pandas()を一度呼んでおくと、applyの代わりにprogress_applyが使えるようになります。中身は同じ処理で、進捗が出るだけです。
pandasの基本から確かめたい方は、こちらが土台になります。【関連記事】pandas入門 データ処理をやってみよう
enumerateやzipと組み合わせるときの落とし穴¶
tqdmを他の関数と重ねると、うっかりバーが消えることがあります。
よくあるのがtqdm(enumerate(users))という書き方です。enumerateを通した時点で長さの情報が隠れてしまい、パーセントが出なくなります。
公式のREADMEでも、この場合はenumerate(tqdm(users))と書くよう案内されています。包む順番を入れ替えるだけで直ります。
zipも同じで、zip(tqdm(a), b)のように中身のほうを包みます。番号付きの繰り返しそのものに不安がある方は、こちらで基本を押さえておくと安心です。【関連記事】Pythonのenumerate関数の使い方とは?インデックス管理を簡単にする書き方
実務で気をつけていること¶
便利な一方で、入れる場所を間違えると迷惑な道具にもなります。私が現場で決めているのは2つだけです。
ひとつは、自動実行するスクリプトではバーを止めることです。バーは同じ行を何度も書き換えるので、画面のないcronやCIでは更新のたびに新しい行として記録され、ログが数千行に膨らみます。
以前これをやってしまい、翌朝のログが進捗バーで埋まって肝心のエラーが埋もれました。それ以来、disableに条件を渡すようにしています。
import sys
for user in tqdm(users, disable=not sys.stderr.isatty()):
...
isatty()は、出力先が人の見る画面かどうかを判定します。人がいるときだけバーを出す、という意味の一行です。
もうひとつは、1回が一瞬で終わるループには付けないことです。tqdmの追加コストは1回あたりおよそ60ナノ秒とされていますが、数百万回まわす計算では表示の更新そのものが無視できなくなります。
よく使う設定を、最後に表でまとめておきます。
| 指定 | 役割 |
|---|---|
desc='処理中' |
バーの左に出すラベル |
total=1000 |
全体の数を自分で教える |
unit='件' |
数える単位の表示を変える |
disable=True |
バーを出さない |
leave=False |
終わったらバーの行を消す |
leave=Falseは、入れ子のループで外側だけを残したいときに使います。画面が静かになるので、私はよく使います。
なお、JupyterやGoogle Colabで使うならfrom tqdm.auto import tqdmと書いておくのがおすすめです。環境に合わせて、ノートブック用の表示か端末用の表示かを自動で選んでくれます。
まとめ¶
tqdmは、繰り返したいものを包むだけで進捗バーを出してくれるライブラリです。元のコードを書き換える必要がほとんどありません。
全体の数がわかる相手ならパーセントと残り時間まで出ますし、わからない相手にはtotalで教えれば同じ表示になります。
ループの中で何か伝えたいときはprintではなくtqdm.write()を使う。自動実行するスクリプトではdisableで黙らせる。この2つを覚えておけば、困る場面はほとんどありません。
ちなみに2026年9月時点の最新版は4.70.1で、Python 3.8以上に対応しています。名前はアラビア語で進捗を意味するtaqaddumに由来する、と作者自身が説明しています。
まずは、いま手元でいちばん時間のかかっているループをtqdm()で包んでみてはいかがでしょうか。待ち時間は変わらなくても、待つ気持ちはずいぶん楽になります。ここまでお読みいただきありがとうございました。