Last Updated on 2024-07-27 by Clay
在我們撰寫 Python 程式碼的時候,有時候,我們會希望我們正在做的工作能夠視覺化顯示『進度條』,好方便我們掌握我們的程式執行到哪裡了。如果是這種需求的話,除了自己寫進度條外,也可以考慮使用 Python 當中相當知名的進度條模組——tqdm。
你也可以去到 tqdm 的官方網站:https://tqdm.github.io/ 閱讀更多詳細的說明。
實際上,tqdm 也是阿拉伯語當中的『進度條』的意思,只需要安裝了這個 Python 的套件,我們就可以在任何需要迭代的地方使用 tqdm 來顯示進度條。
那麼,以下開始介紹怎麼使用 tqdm 吧!
tqdm 使用方法
由於這個模組並不是 Python 內建的,所以如果我們第一次使用,我們可以使用 pip
指令進行安裝:
pip3 install tqdm
安裝好後我們來看段 Sample Code:
import time
from tqdm import tqdm
for i in tqdm(range(1000)):
time.sleep(0.01)
Output:
100%|██████████| 1000/1000 [00:10<00:00, 95.76it/s]
我們會看到進度條被逐漸新增的模樣。最開頭自然是我們心心念念的進度條,後面的 1000/1000 其實是『當前迭代次數 / 總共迭代次數』,而 00:10<00:00 則是『當前執行時間 / 預估剩餘時間』,最後的 95.76it/s 則是一秒執行了幾次迭代,有時也會看到反過來的 s/it 則是代表一次迭代時間執行了幾秒。
另外解釋一下,time.sleep()
函式的功能是讓程式每次執行到這裡時停止 0.01 秒,總共 1000 次。如果不設個暫停的話,恐怕會看到 tqdm 所印出來的進度條一下子就被執行完了、這樣就無法模擬真正的執行狀況了,大家可以實際測試看看。
而 tqdm()
對於所有可迭代物件都是有效的,比方說對單純的列表(List)也可以進行遞迴。
import time
from tqdm import tqdm
arr = [1, 2, 3, 4, 5]
for i in tqdm(arr):
time.sleep(1)
Output:
100%|█████████████████████████████████████████████| 5/5 [00:05<00:00, 1.00s/it]
另外,tqdm 也提供了一個 trange()
的功能來直接實現對於指定長度迭代的顯示,可以想像成 trange(x)
等價於 tqdm(range(x))
。
import time
from tqdm import trange
for i in trange(1000):
time.sleep(0.01)
Output:
100%|██████████| 1000/1000 [00:10<00:00, 95.72it/s]
以上就是 tqdm 的基本使用方式,非常淺顯易懂。
tqdm 進階技巧
以下我會提及一些方便的小技巧:
- 更改進度條長度
- 更改單位
- 更改進度條格式
- 更改進度條說明
- 更改進度條的顏色
- 使用 .update() 自訂要何時更新、更新多少
更改進度條長度
tqdm 本來會自動偵測螢幕寬度,自適應成剛剛好的進度條寬度;但是我們也可以手動設定:
for i in tqdm(1000, ncols=100):
time.sleep(0.01)
Output:
上方 ncols=None
為沒有設定的狀況、下面的 ncols=100
則是規定了寬度的情況。
更改單位
這個功能我不是很常用,但是在做深度學習訓練時,總難免會想要將進度的單位從 iter 改成比較直覺的 epoch 或 step。這時候我們就可以設定 unit
參數。
for i in tqdm(range(1000), unit="epoch"):
time.sleep(0.01)
Output:
100%|██████████| 1000/1000 [00:10<00:00, 98.27epoch/s]
更改進度條格式
tqdm 中的 bar_format
允許我們自定義進度條的顯示格式。以下是我們可以使用的格式標記:
- {l_bar}:左側進度條部分,包括描述和進度條
- {bar}:進度條本身
- {r_bar}:右側進度條部分,包括計數器、時間估算等
- {n}:當前進度數量
- {n_fmt}:格式化後的當前進度數量
- {total}:進度條總長度
- {total_fmt}:格式化後的進度條總長度
- {elapsed}:已用時間
- {elapsed_s}:已用時間(秒)
- {elapsed_td}:已用時間(timedelta 格式)
- {remaining}:剩餘時間
- {remaining_s}:剩餘時間(秒)
- {remaining_td}:剩餘時間(timedelta 格式)
- {percentage}:完成百分比
- {rate}:每秒處理數
- {rate_fmt}:格式化後的每秒處理數
- {rate_inv}:每項所需時間
- {rate_inv_fmt}:格式化後的每項所需時間
- {postfix}:進度條後綴字串
比方說,我喜歡格式乾淨一點的進度條,我可以這樣寫:
for i in tqdm(range(100), bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}"):
time.sleep(0.01)
Output:
100%|███████████████| 100/100
更改進度條說明
我們也可以替 tqdm 進度條加上一些說明,首先來看看最單純的說明設定方式:
for i in tqdm(range(1000), desc="Processing..."):
time.sleep(0.01)
Output:
Processing...: 100%|███████████| 1000/1000 [00:10<00:00, 98.77it/s]
這在有雙重迴圈甚至深層結構的迴圈中非常好用,至少我們可印出這是第幾個進度條,比方說 epoch-1, epoch-2,... , epoch-n。
另一個好用的技巧在於,我們也可以在迭代過程中改變進度條的說明!這樣可以更即時地掌握處理資料的狀態。
import time
import random
from tqdm import tqdm
# Initialize counters
good = 0
bad = 0
# Create progress bar
pbar = tqdm(range(1000), desc="Starting")
# Simulate a loop with dynamic desc update
for _ in pbar:
# Simulate work
time.sleep(0.01)
# Randomly choose True or False
if random.choice([True, True, False]):
good += 1
else:
bad += 1
# Update progress bar description
pbar.set_description(f"Good: {good} Bad: {bad}")
# Close progress bar
pbar.close()
Output:
Good: 650 Bad: 350: 100%|████████████████| 1000/1000 [00:10<00:00, 98.17it/s]
過程中我會隨機地選取 True 或 False,並且把 tqdm 進度條賦值成的 pbar
使用 .set_description()
來更新當前處理狀況。
這樣如果在跑迭代的過程中,有什麼需要即時看到的資訊也可以直接看到。
更改進度條的顏色
在跑進度條的時候,若是單純的預設顏色實在不起眼,我們也可以設定顏色給 colour
參數;不過,在早期的 tqdm 並沒有這個功能,需要到一定的版本(例如 4.38.0)以上才支援這個參數。
for i in tqdm(range(1000), colour="green"):
time.sleep(0.01)
Output:
使用 .update() 自訂要何時更新、更新多少
另外,tqdm 不僅僅是可以使用在 for 迴圈裡頭而已,我們甚至可以自己決定在什麼時候更新進度條、推進迭代進度。以下是一段簡單的範例:
times = 0
progress = tqdm(total=1000)
while times < 1000:
progress.update(1)
times += 1
sleep(0.01)
Output:
100%|██████████| 1000/1000 [00:10<00:00, 95.68it/s]
看起來是一樣的功能,但我們這次可以自己選擇在什麼時候將 progress.update()
,也可以自己選擇 update()
的數量。
最後,再順帶一提:如果是在 Google Colab 或是 Kaggle Notebook、Jupyter Notebook 等平台上,tqdm 的寫法是得用他們提供的 notebook 包:
from tqdm.notebook import tqdm
實作自己的進度條
當然,除了使用 tqdm 模組來實現自己的進度條外,我們當然可以自己來手動寫屬於自己的進度條!
以下我來拋磚引玉一下,讓各位見笑了:
from time import sleep
curr = 0
total = 1000
total_block_num = 50
for n in range(total):
curr += 1
block_num = int(curr * total_block_num / total)
space_num = total_block_num - block_num
print(f"\r[{curr / total * 100:.2f}%]: [{'█' * block_num}{' ' * space_num}]", end="")
sleep(0.01)
Output:
[100.00%]:[██████████████████████████████████████████████████];
進階挑戰
接下來我也嘗試把上述的方法寫成一個函式,可以輸入可迭代物件產生進度條,並且也學了 tqdm 一樣設定了顏色參數。
from time import sleep
import sys
import itertools
def progress_bar(iterable, total=None, total_block_num=50, color="default"):
if total is None:
total = len(iterable)
color_codes = {
"default": "\033[0m", # Default
"red": "\033[91m", # Red
"green": "\033[92m", # Green
"yellow": "\033[93m", # Yellow
"blue": "\033[94m", # Blue
"magenta": "\033[95m", # Magenta
"cyan": "\033[96m", # Cyan
"white": "\033[97m", # White
}
if color not in color_codes:
color = "default"
color_code = color_codes[color]
for curr, item in enumerate(iterable, 1):
block_num = int(curr * total_block_num / total)
space_num = total_block_num - block_num
progress_str = f"\r{color_code}[{float(curr / total * 100):.2f}%]: [{'█' * block_num}{' ' * space_num}]\033[0m"
print(progress_str, end="")
yield item
print() # Move to the next line after completion
if __name__ == "__main__":
for _ in progress_bar(range(1000), color="cyan"):
sleep(0.01)
Output:
大家若有興趣,也不妨也寫寫看自己的進度條吧!我目前的進度條寫法還比較死,沒辦法如同 tqdm 一般任意選擇更新的時間點與更新量 —— 但這也是我們可以後續加強的地方。
挑戰看看寫成 class 類別,或是可以讓使用者使用 update() 函式來更新進度吧?
我有空的時候,也想進行這樣的挑戰呢。