簡體   English   中英

Python中的雙進度條

[英]Double Progress Bar in Python

有沒有辦法在 Python 中創建雙進度條? 我想在彼此內部運行兩個循環。 對於每個循環,我想要一個進度條。 我的程序看起來像:

import time
for i1 in range(5):
    for i2 in range(300):
        # do something, e.g. sleep
        time.sleep(0.01)
        # update upper progress bar
    # update lower progress bar

中間某處的輸出應該類似於

50%|############################                                  |ETA: 0:00:02
80%|##################################################            |ETA: 0:00:04

已經存在的非常酷的進度條模塊似乎不支持這一點。

使用tqdm 的嵌套進度條功能,這是一個開銷極低、非常可定制的進度條庫:

$ pip install -U tqdm

然后:

from tqdm import tqdm
# from tqdm.auto import tqdm  # notebook compatible
import time
for i1 in tqdm(range(5)):
    for i2 in tqdm(range(300), leave=False):
        # do something, e.g. sleep
        time.sleep(0.01)

leave=False是可選的 - 需要在完成后丟棄嵌套條。)

您也可以使用from tqdm import trange ,然后將tqdm(range(...))替換為trange(...) 你也可以讓它在筆記本上工作

或者,如果您只想要一個欄來監控所有內容,您可以使用tqdmitertools.product版本:

from tqdm.contrib import itertools
import time
for i1, i2 in itertools.product(range(5), range(300)):
    # do something, e.g. sleep
    time.sleep(0.01)

我基本上只是想補充@casper.dcl 的答案。 在稍微不同的情況下,您有兩個嵌套的 for 循環並且只需要一個進度條,您可以執行以下操作。

from tqdm import tqdm
import time
n = 5
m = 300
with tqdm(total=n * m) as pbar:
    for i1 in tqdm(range(n)):
        for i2 in tqdm(range(m)):
            # do something, e.g. sleep
            time.sleep(0.01)
            pbar.update(1)

我知道這不是問題,但它可能對某些人仍然有幫助。

它需要您移動光標位置。 我給你寫了一個 hacky 的東西來做這件事。

該腳本依賴於進度條模塊假設您在新行上繪制進度條這一事實。 通過簡單地向上移動光標(使用“向上移動光標 1 行”的轉義碼)和向下移動(只使用換行符。我也可以使用轉義碼,但換行符更容易和更快),可以保持多個進度酒吧。

import progressbar, time, sys

def up():
    # My terminal breaks if we don't flush after the escape-code
    sys.stdout.write('\x1b[1A')
    sys.stdout.flush()

def down():
    # I could use '\x1b[1B' here, but newline is faster and easier
    sys.stdout.write('\n')
    sys.stdout.flush()

# Total bar is at the bottom. Move down to draw it
down()
total = progressbar.ProgressBar(maxval=50)
total.start()

for i in range(1,51):
    # Move back up to prepare for sub-bar
    up()

    # I make a new sub-bar for every iteration, thinking it could be things
    # like "File progress", with total being total file progress.
    sub = progressbar.ProgressBar(maxval=50)
    sub.start()
    for y in range(51):
        sub.update(y)
        time.sleep(0.005)
    sub.finish()

    # Update total - The sub-bar printed a newline on finish, so we already
    # have focus on it
    total.update(i)
total.finish()

這當然有點hacky,但它完成了工作。 我希望它是有用的。

使用啟蒙

import time
import enlighten

manager = enlighten.get_manager()
ticks = manager.counter(total=100, desc="Ticks", unit="ticks", color="red")
tocks = manager.counter(total=20, desc="Tocks", unit="tocks", color="blue")

for num in range(100):
    time.sleep(0.1)  # Simulate work
    print("The quick brown fox jumps over the lazy dog. {}".format(num))
    ticks.update()
    if not num % 5:
        tocks.update()

manager.stop()

在此處輸入圖像描述

游戲有點晚了,但這是一個只使用 tqdm 的答案

import re
from time import sleep
from tqdm import trange

class DescStr:
    def __init__(self):
        self._desc = ''

    def write(self, instr):
        self._desc += re.sub('\n|\x1b.*|\r', '', instr)

    def read(self):
        ret = self._desc
        self._desc = ''
        return ret

    def flush(self):
        pass


rng_a = trange(10)
desc = DescStr()
for x in rng_a:
    for y in trange(10, file=desc, desc="Y"):
        rng_a.set_description(desc.read())
        sleep(0.1)

產生:

Y:  90%|######### | 9/10 [00:00<00:00,  9.55it/s]: 100%|##########| 10/10 [00:10<00:00,  

受此答案的啟發,我還嘗試了啟發python 庫並編寫了簡單的輔助函數pit()用於將迭代器包裝在 for 循環(代碼頂部)中,並提供了使用示例(代碼底部)以及實時終端截屏。

與鏈接答案的主要區別在於, pit()允許在 for-loop 中使用來包裝迭代器,而不是使用手動.update()方法,這種迭代器包裝功能在englighten中缺乏,這就是我決定實現自己的原因.

正如在 Accepted answer 中看到的那樣,其他著名的進度條庫(例如tqdm已經具有將迭代器包裝在 for 循環中以及嵌套循環中的多個進度條的功能。

在 Linux 和 Windows 中都可以彩色工作。

在線嘗試!

# Helper Progress Iterator
# Needs: python -m pip install enlighten

def pit(it, *pargs, **nargs):
    import enlighten
    global __pit_man__
    try:
        __pit_man__
    except NameError:
        __pit_man__ = enlighten.get_manager()
    man = __pit_man__
    try:
        it_len = len(it)
    except:
        it_len = None
    try:
        ctr = None
        for i, e in enumerate(it):
            if i == 0:
                ctr = man.counter(*pargs, **{**dict(leave = False, total = it_len), **nargs})
            yield e
            ctr.update()
    finally:
        if ctr is not None:
            ctr.close()


####### Usage Example ########

import time

def Generator(n):
    for i in range(n):
        yield i

for i in pit(range(2), color = 'red'):
    for j in pit(range(3), color = 'green'):
        for k in pit(Generator(4), total = 4, color = 'blue'):
            for l in pit(Generator(5)):
                print(i, j, k, l)
                time.sleep(0.05)

輸出(+ ascii-video ):

ASCII

這可以使用atpbar輕松完成。

例如:

import time, random
from atpbar import atpbar

for i in atpbar(range(4), name='outer'):
    n = random.randint(1000, 10000)
    for j in atpbar(range(n), name='inner {}'.format(i)):
        time.sleep(0.0001)

上面的代碼嵌套for循環。 外循環迭代四次。 對於外循環的每次迭代,內循環都會迭代隨機選擇的次數。 隨着循環完成,內部循環的進度條會向上移動。 活動進度條停留在底部。 進度條的快照可能看起來像

 100.00% :::::::::::::::::::::::::::::::::::::::: |     3287 /     3287 |:  inner 0
 100.00% :::::::::::::::::::::::::::::::::::::::: |     5850 /     5850 |:  inner 1
  50.00% ::::::::::::::::::::                     |        2 /        4 |:  outer  
  34.42% :::::::::::::                            |     1559 /     4529 |:  inner 2

我為 Python3.6+ 繪制了一個獨立的、簡單的進度條。 沒有 tqdm,沒有其他依賴項,沒有 hacks。

def myprogress(current, whole=1, n=30, bars=u'▕▏▎▍▌▋▊▉', full='▉', empty='▕'): 
    """ current and whole can be an element of a list being iterated, or just two numbers """
    p = (whole.index(current))/len(whole)+1e-9 if type(whole)==list else current/whole+1e-9 
    return f"{full*int(p*n)}{bars[int(len(bars)*((p*n)%1))]}{empty*int((1-p)*n)} {p*100:04.1f}%" 

在純 Python 中,很難重寫最后一行。 但是您可以將兩個條彼此相鄰堆疊。 (單行解決方案也可以很好地工作,例如 GUI 中的窗口裝飾!)

for x in range(300):  ## accepting numerical value
     print(myprogress(x/300), ' '*5, myprogress(x/321), end='\r')
     for busyloop in range(10**5): pass

它以兩個數字的比率來計算進度,或者在被迭代的列表中找到一個元素。 (如果你迭代一個 numpy.array,很容易將它轉換為一個列表。)所以這也是可能的:

l = ['apples', 'bananas', 'cherries', 'durians']  ## accepting an element from list being iterated
for x in l: 
     print(myprogress(x, whole=l), ' '*5, myprogress(x, whole=l), end='\r')
     for busyloop in range(10**7): pass

在第一個示例中,您得到:

▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▎▕▕▕▕▕▕▕▕ 71.0%      ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▕▕▕▕▕▕▕▕▕▕ 66.4%

                                                                                                    

它是如此簡單,它必須是公共領域。

PS:如果你喜歡吃豆子,你可以將進度從右到左並使用:bars='ᗦᗠᗦᗠᗦᗠ'

這是一個顯示外部和內部循環進度的簡單方法:

from tqdm import tqdm
from time import sleep

pbar = tqdm(range(10))
for i in pbar:
    for j in range(20):
        pbar.set_postfix({'inner': j})
        sleep(.2)

這不是您所要求的:這里的內循環僅顯示為遞增數字,而進度條顯示外循環進度。 但它是嵌套循環的有用可視化。

這是一個快照:

 30%|███       | 3/10 [00:14<00:33,  4.77s/it, inner=12]

隨着外部循環的進度條緩慢前進,“內部”計數器不斷增加。

更新:

您可以將此解決方案與 dominecf 的解決方案結合使用。 以下使用 tqdm 作為外循環,並使用 dominecf 的函數集成內循環(稍作修改):

import tqdm
import time

def myprogress(curr, N, width=10, bars = u'▉▊▋▌▍▎▏ '[::-1],
               full='█', empty=' '): 
    p = curr / N 
    nfull = int(p * width)
    return "{:>3.0%} |{}{}{}| {:>2}/{}"\
        .format(p, full * nfull,
                bars[int(len(bars) * ((p * width) % 1))],
                empty * (width - nfull - 1),
                curr, N)


pbar = tqdm.tqdm(range(10),
                 bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}')
for i in pbar:
    for j in range(20):
        pbar.set_postfix_str(myprogress(j, 20))
        time.sleep(.2)

這是一個快照:

 30%|███       | 3/10 [00:14<00:34,  4.90s/it, 60% |██████    | 12/20]                                                                                                                                      

受到@dominecf 的簡單回答的啟發,只是為了好玩,我實現了一個輔助包裝函數pbiter() ,它可以在循環中使用以顯示任何可迭代對象的進度。 pbiter()使用@dominecf 的myprogress()實現。

不要過多判斷這個答案,它只是為了在純Python中從頭開始實現進度的黑客樂趣,這個答案並不意味着在任何生產環境中使用,在實際應用中使用tqdmenlighten模塊來做進度。

請參閱我對同一問題的其他答案,該答案顯示了如何使用enlighten模塊取得進展。

此答案中的pbiter()可以非常簡單地與嵌套循環中的任何可迭代對象一起使用,如下所示:

for a in pbiter(range(12)):
    for b in pbiter(generator_nums(13)):
        for c in pbiter(generator_nums(7), total = 7):
            time.sleep(0.03)

進度條總長度由len(it)計算,如果它可用於迭代(例如,對於range(start, stop, step) ,它始終可用),或者通過提供total = ...參數,否則進度呈指數衰減乘數0.1 (顯示了一個很好的近似值)。 在第二個循環上方的三個示例中,嵌套循環具有這種指數行為。

完整代碼如下。 請參閱代碼后的 ascii-video。

在線嘗試!

def myprogress(current, whole=1, n=30, bars=u'▕▏▎▍▌▋▊▉', full='▉', empty='▕'): 
    """ current and whole can be an element of a list being iterated, or just two numbers """
    p = (whole.index(current))/len(whole)+1e-9 if type(whole)==list else current/whole+1e-9 
    return f"{full*int(p*n)}{bars[int(len(bars)*((p*n)%1))]}{empty*int((1-p)*n)} {p*100:>6.2f}%" 

def pbiter(it, *, total = None, width = 36, _cfg = {'idx': -1, 'pbs': {}, 'lline': 0}):
    try:
        total = total or len(it)
    except:
        total = None
    
    _cfg['idx'] += 1
    idx = _cfg['idx']
    pbs = _cfg['pbs']
    pbs[idx] = [0, total, 0]
    
    def Show():
        line2 = ' '.join([
            myprogress(e[1][0], max(e[1][0], e[1][1] or
                max(1, e[1][0]) / max(.1, e[1][2])), width // len(pbs))
            for e in sorted(pbs.items(), key = lambda e: e[0])
        ])
        line = line2 + ' ' * (max(0, _cfg['lline'] - len(line2)) + 0)
        print(line, end = '\r', flush = True)
        _cfg['lline'] = len(line2)
    
    try:
        Show()
        for e in it:
            yield e
            pbs[idx][0] += 1
            pbs[idx][2] += (1. - pbs[idx][2]) * .1
            Show()
        pbs[idx][2] = 1.
        Show()
    finally:
        del pbs[idx]

def test():
    import time

    def generator_nums(cnt):
        for i in range(cnt):
            yield i

    for a in pbiter(range(12)):
        for b in pbiter(generator_nums(13)):
            for c in pbiter(generator_nums(7), total = 7):
                time.sleep(0.03)

test()

ASCII 視頻輸出(另見 asciinema視頻頁面):

在此處輸入圖像描述

如果由於某種原因您沒有循環並且仍然想使用我的pbiter() ,那么您可以通過常規的內置next()操作來使用它,如下所示:

# Create 3 progress bars, they are at 0% point now
a = pbiter(range(5))
b = pbiter(range(4))
c = pbiter(range(3))
# Some lines of code later, advance progress "a"
next(a)
# And later ...
next(b)
# And later ...
next(b)
# Later ...
next(a); next(c)
# Later ...
next(c); next(b)

換句話說,您可以在任何代碼位置以任何順序手動創建和推進進度條。

曾經這里是 @yurenchen 的答案(已被刪除),該廣告宣傳豐富的庫,它具有在 docs中描述的進度條例程。

豐富的庫可以通過python -m pip install rich

顯示三個不同顏色的進度條堆棧的最小示例是:

在線嘗試!

import time

from rich.progress import Progress

with Progress() as progress:

    task1 = progress.add_task("[red]Downloading...", total=1000)
    task2 = progress.add_task("[green]Processing...", total=1000)
    task3 = progress.add_task("[cyan]Cooking...", total=1000)

    while not progress.finished:
        progress.update(task1, advance=0.5)
        progress.update(task2, advance=0.3)
        progress.update(task3, advance=0.9)
        time.sleep(0.02)

它產生以下彩色控制台輸出(+ aciinema link ):

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM