小さな手を動かして大金を稼いで、親指を立ててください!
導入
困った
データ サイエンティストが生産性を向上させるために Python で同時プログラミングを使用することは珍しいことではありません。バックグラウンドでさまざまな子プロセスや同時スレッドを監視して、計算や IO バインドされたタスクを順番に維持するのは、常に満足感があります。
しかし、もう 1 つ気になるのは、数百または数千のファイルを同時に処理したり、数百または数千のプロセスをバックグラウンドで実行したりするときに、いくつかのタスクが密かにハングアップし、コード全体が完了しないまま永遠に実行されるのではないかと常に心配することです。コードが現在どこで実行されているかを知るのも困難です。
さらに悪いことに、空白の画面を見ていると、コードの実行にどれくらい時間がかかるのか、ETA が何なのかを知るのが困難です。これは私の仕事のスケジュールを調整する能力に非常に悪影響を及ぼします。
したがって、コードがどこで実行されるのかを知る方法が必要です。
既存手法
より伝統的な方法は、タスク間でメモリ領域を共有し、このメモリ領域にカウンタを置き、タスクの終了時にこのカウンタを +1 させ、スレッドを使用してこのカウンタの値を継続的に出力することです。
これは決して良い解決策ではありません。一方で、既存のビジネス ロジックにカウント用のコードを追加する必要がありますが、これは「低結合、高凝集」の原則に違反します。一方で、スレッドの安全性の問題により、不必要なパフォーマンスの問題が発生するため、ロック機構には細心の注意を払う必要があります。
tqdm
ある日、プログレスバーを使用してコードの進行状況を視覚化する tqdm ライブラリを発見しました。プログレスバーを使用して、asyncio タスクの完了と ETA を視覚化できますか?
次に、この記事[1]では、すべてのプログラマーが並行タスクの進行状況を監視できるように、この方法を皆さんと共有します。
非同期
始める前に、Python asyncio についての背景を理解していただきたいと思います。私の記事では、asyncio [2]のいくつかの一般的な API の使用法について説明しています。これは、tqdm の設計をより深く理解するのに役立ちます。
tqdmの概要
公式サイトにも記載されているように、tqdmは円形のプログレスバーを表示するツールです。使いやすく、高度にカスタマイズ可能で、リソースの使用量が少ないです。
一般的な使用法は、反復可能オブジェクトを tqdm コンストラクターに渡すことで、次のような進行状況バーが表示されます。
from time import sleep
from tqdm import tqdm
def main():
for _ in tqdm(range(100)):
# do something in the loop
sleep(0.1)
if __name__ == "__main__":
main()
または、ファイルの読み取り中に進行状況バーの進行状況を手動で参照して更新することもできます。
import os
from tqdm import tqdm
def main():
filename = "../data/large-dataset"
with (tqdm(total=os.path.getsize(filename)) as bar,
open(filename, "r", encoding="utf-8") as f):
for line in f:
bar.update(len(line))
if __name__ == "__main__":
main()
tqdm と async を統合する
全体として、tqdm は非常に使いやすいです。ただし、tqdm と asyncio の統合に関する詳細については、GitHub で必要です。そこでソースコードを調べて、tqdm が asyncio をサポートしているかどうかを確認しました。
幸いなことに、tqdm の最新バージョンでは、クラス tqdm_asyncio を提供するパッケージ tqdm.asyncio が提供されています。
tqdm_asyncio クラスには 2 つの関連メソッドがあります。1 つは tqdm_asyncio.as_completed です。ソース コードからわかるように、これは asyncio.as_completed のラッパーです。
@classmethod
def as_completed(cls, fs, *, loop=None, timeout=None, total=None, **tqdm_kwargs):
"""
Wrapper for `asyncio.as_completed`.
"""
if total is None:
total = len(fs)
kwargs = {}
if version_info[:2] < (3, 10):
kwargs['loop'] = loop
yield from cls(asyncio.as_completed(fs, timeout=timeout, **kwargs),
total=total, **tqdm_kwargs)
もう 1 つは tqdm_asyncio.gather で、ソース コードからわかるように、asyncio.gather の機能をシミュレートする tqdm_asyncio.as_completed の実装に基づいています。
@classmethod
async def gather(cls, *fs, loop=None, timeout=None, total=None, **tqdm_kwargs):
"""
Wrapper for `asyncio.gather`.
"""
async def wrap_awaitable(i, f):
return i, await f
ifs = [wrap_awaitable(i, f) for i, f in enumerate(fs)]
res = [await f for f in cls.as_completed(ifs, loop=loop, timeout=timeout,
total=total, **tqdm_kwargs)]
return [i for _, i in sorted(res)]
そこで次に、これら 2 つの API の使い方について説明します。始める前に、いくつかの準備作業を行う必要があります。ここでは、ランダムなスリープ時間で同時タスクをシミュレートする簡単なメソッドを書きました。
import asyncio
import random
from tqdm.asyncio import tqdm_asyncio
class AsyncException(Exception):
def __int__(self, message):
super.__init__(self, message)
async def some_coro(simu_exception=False):
delay = round(random.uniform(1.0, 5.0), 2)
# We will simulate throwing an exception if simu_exception is True
if delay > 4 and simu_exception:
raise AsyncException("something wrong!")
await asyncio.sleep(delay)
return delay
次に、2000 個の同時タスクを作成し、使い慣れた asyncio.gather メソッドの代わりに tqdm_asyncio.gather を使用して、進行状況バーが機能しているかどうかを確認します。
async def main():
tasks = []
for _ in range(2000):
tasks.append(some_coro())
await tqdm_asyncio.gather(*tasks)
print(f"All tasks done.")
if __name__ == "__main__":
asyncio.run(main())
または、 tqdm_asyncio.gather を tqdm_asyncio.as_completed に置き換えて、再試行してみましょう。
async def main():
tasks = []
for _ in range(2000):
tasks.append(some_coro())
for done in tqdm_asyncio.as_completed(tasks):
await done
print(f"The tqdm_asyncio.as_completed also works fine.")
if __name__ == "__main__":
asyncio.run(main())
参照
ソース:https://towardsdatascience.com/using-tqdm-with-asyncio-in-python-5c0f6e747d55
[2]非同期:https://github.com/Jwindler/Ice_story
この記事はmdniceマルチプラットフォームによって公開されています