nikkie-ftnextの日記

イベントレポートや読書メモを発信

Python でプログレスバーの tqdm、イテラブルをラップしてプログレスバーを出力するイテラブルを返すのがうまいな〜

はじめに

(仕様の)迷宮へ誘いましょう... nikkie (UUID 28fb3f96-a221-462c-93bd-567b431715b9) です。

Python ライブラリ tqdm のtqdm.tqdm(iterable)についての気づきをしたためます。
これはすごいですよ...

目次

Python でプログレスバーを表示したい

tqdm というライブラリがあります。
大量のデータを処理する際に、進捗状況を表示してくれて大変便利です。

過去に HTTP クライアントでダウンロードする際のプログレスバーを記事にしました。

余談ですが、tqdm は標準出力・標準エラー出力に限らず、多様な出力先をサポートしています1

tqdm が返すのは、イテラブル!

今回気づいた点です2

% uv run --with tqdm python
Python 3.14.3 free-threading build

>>> import tqdm
>>> tqdm.__version__
'4.68.1'
>>> tqdm_obj = tqdm.tqdm("abcde")
  0%|                                                     | 0/5 [00:21<?, ?it/s]
>>> type(tqdm_obj)
<class 'tqdm.std.tqdm'>
>>> from collections.abc import Iterable, Iterator
>>> isinstance(tqdm_obj, Iterable)
True
>>> isinstance(tqdm_obj, Iterator)
False

(マシンに設定している cooldown の関係で 4.68.1 と思われます)

イテラブルなので、tqdm.tqdm(iterable)iterable同様、for文に書けます

__next__()が実装されていないので、イテレータではないという判定です)

うまいと思った点:データ側でプログレスバーを出力する

きっかけは不慣れな clojure で数十件のデータを処理していて、進捗を知りたくなったこと。
データを処理する関数側に進捗状況を出力するコードを入れようと最初は思ったのですが、それだと色々な関数に似たような進捗出力コードが入っていきますよね。
であればデータ側に、次のデータを返すイテレータ + どこまで返したか進捗を出力するように実装したいなと思いました。
関数は変えずに、どのような関数にもプログレスバーが導入できます!

ここまで考えて、「そうか、Python の tqdm がやっているのは、イテラブルを包んで、プログレスバー出力だけ追加することなんだな」と気づきました。

プログレスバーを出力するイテラブルの実装

https://github.com/tqdm/tqdm/blob/v4.68.2/tqdm/std.py#L242

Decorate an iterable object, returning an iterator which acts exactly like the original iterable, but prints a dynamically updating progress bar every time a value is requested.

まさしく元のイテラブルのように振る舞いつつ、プログレスバーも出力します。
(tqdm 作者はイテレータと言っていますが、先のisinstanceの結果から私はイテラブルだと思っています)

__init__()の実装(抜粋)
https://github.com/tqdm/tqdm/blob/v4.68.2/tqdm/std.py#L1048

class tqdm(Comparable):
    def __init__(self, iterable=None, file=None, ...):

        self.iterable = iterable
        self.fp = file

        if not gui:
            # Initialize the screen printer
            self.sp = self.status_printer(self.fp)

(screen printer とstatus_printer、微妙に一致しないですね)

__iter__()を持つのでイテラブル!(コードは抜粋です)
https://github.com/tqdm/tqdm/blob/v4.68.2/tqdm/std.py#L1160

class tqdm(Comparable):
    def __iter__(self):
        iterable = self.iterable

        for obj in iterable:
            yield obj
            # Update and possibly print the progress bar.
            self.update(n - last_print_n)

update()を辿っていくと、sp(screen printer)を呼び出しています。
https://github.com/tqdm/tqdm/blob/v4.68.2/tqdm/std.py#L1495

self.sp(self.__str__() if msg is None else msg)

static method status_printer()でステータスを出力する関数を返しています。
https://github.com/tqdm/tqdm/blob/v4.68.2/tqdm/std.py#L437

終わりに

Python の tqdm、任意のイテラブルに、プログレスバー出力ロジックだけを追加したイテラブルとするアイデアが秀逸だと思いました。
デザインパターンの Decorator ですね3


  1. https://tqdm.github.io/docs/contrib.discord/ などドキュメントにもあります
  2. イテラブル(やイテレータ)については過去記事もどうぞ
  3. マンガでわかる Decorator #デザインパターン - Qiita