[英]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(...)
。 你也可以让它在笔记本上工作。
或者,如果您只想要一个栏来监控所有内容,您可以使用tqdm
的itertools.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 ):
这可以使用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中从头开始实现进度的黑客乐趣,这个答案并不意味着在任何生产环境中使用,在实际应用中使用tqdm
或enlighten
模块来做进度。
请参阅我对同一问题的其他答案,该答案显示了如何使用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.