はじめに #
Pythonの関数内でfor文を使って重たい処理を回すときに、yield
文を使って進捗状況を呼び出し元に返す方法を解説します。
想定する状況:
- ディープラーニングやデータ送受信などの時間が掛かる処理を
for
文で実行する。 - 処理の進捗をTkinter, PyQt, Django, FlaskなどでGUI表示(プログレスバー等)したい。
進捗を表示する処理をfor
文の中に入れることも実装の一つとして可能です。しかし、大規模なアプリケーションではGUIとビジネスロジックを分離することが一般的であるため、for
文の中に直接GUIに関するコンポーネントを入れたくありません。
このGUIとビジネスロジックを分離する方法について、具体的な実施例が見つけられなかったため備忘録として残します。
検証環境
- Python 3.11.6
- tqdm 4.66.2
yield文で反復回数を返す #
処理の進捗状況を返すには、Pythonのyield
文を用います。例を以下に示します。
from typing import Iterator
def my_func(n_iteration:int) -> Iterator[int]:
for i in range(n_iteration):
# ここに反復処理する重たい処理を書く
yield i
if __name__=="__main__":
for j in my_func(5):
print(j)
実行結果は以下になります。my_func
内のyield
で返した値が、変数j
に格納されることが分かります。そのため、呼び出し元で、何回目のfor
文ループが実行されているのか把握できます。
0
1
2
3
4
ここでyield
について解説します。yield
はreturn
と似ている構文で、関数の呼び出し元に値を返します。ただし、return
と異なる点として、yield
文では「値を返した後、元の関数の処理に戻る」挙動となります。
tqdmを使用した例 #
進捗を表示できるライブラリtqdmを使用した例を以下に示します。wait_1sec
関数では、forループを1回実行する度に1秒待ちます。
from typing import Iterator
import time
from tqdm import tqdm
def wait_1sec(n_iteration:int) -> Iterator[int]:
for i in range(n_iteration):
time.sleep(1)
yield i
if __name__=="__main__":
n_iter = 5
for _ in tqdm(wait_1sec(n_iter), total=n_iter):
pass
実行結果は以下になります(実際はプログレスバーが増える様子がアニメーションで表示されます)。
100%|██████████████████████████| 5/5 [00:05<00:00, 1.00s/it]
tqdmの仕様上、呼び出し元のfor
ループが実行されるだけでプログレスバーが増えていくため、yield
文でループ回数を返す意味はありません。ただし、GUIライブラリによってはループ回数が必要になるかもしれないため、ループ回数を返した方がGUIの変更に対してロバスト性があると思います。