Inspired by simplicity of answer of @dominecf, just for fun I implemented a helper wrapper function pbiter()
that can be used in loops to show progress for any iterables. pbiter()
uses @dominecf's implementation of myprogress()
.
Note. See also my other two answers in this thread about progress bar - using enlighten and using rich lib.
Don't judge this answer too much, it is only for hackery fun of implementing progress from scratch in pure Python, this answer is not meant to be used in any production environment, use tqdm
or enlighten
modules in real application for doing progress.
See my other answer to same Question, that answer shows how to use enlighten
module for progress.
pbiter()
from this answer can be very simply used with any iterables in nested loops like following:
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)
Progress bar total length is figured out either by len(it)
if it is available for iterable (e.g. for range(start, stop, step)
it is always available), or by providing total = ...
param, otherwise progress decays exponentially with multiplier 0.1
(which shows a nice approximation). In three example nested loops above second loop has this exponential behaviour.
Full code below. See ascii-video located after code.
Try it online!
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-video output (see also asciinema video page):

If for some reason you don't have loops and still want to use my pbiter()
, then you can use it through regular built-in next() operation, as following:
# 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)
in other words you can create and advance progress bars manually in any order and at any place of code.