【问题标题】:Double Progress Bar in PythonPython中的双进度条
【发布时间】:2014-05-31 13:33:47
【问题描述】:

有没有办法在 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

已经存在的非常酷的 progressbar 模块似乎不支持这一点。

【问题讨论】:

  • 以上链接失效@ρss

标签: python progress-bar


【解决方案1】:

使用nested progress bars feature of 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(...)。也可以working in a notebook获取。

【讨论】:

  • 它不起作用。要么是因为一个错误,要么它不打算那样工作,但它不断向控制台添加行。
  • 我同意@asu,它不像 OP 在我的控制台中描述的那样工作,它只会产生大量新行
  • 是的,请参阅github.com/tqdm/tqdm/#faq-and-known-issues - 本质上,如果您的控制台不支持它,那么其他答案都不会起作用 - 唯一的选择是要求控制台的维护人员支持回车,或者使用不同的控制台。
  • 如果你使用 Jupyter notebooks,你需要使用 notebook 子模块:from tqdm.notebook import tqdm
  • @asu 要在笔记本中使用它,您需要取消注释 tqdm.auto 行。现代控制台也确实支持编辑前一行。
【解决方案2】:

我基本上只是想补充@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)

我知道这不是问题所在,但它可能对某些人仍有帮助。

【讨论】:

    【解决方案3】:

    这需要您移动光标位置。我给你写了一个 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()
    

    这当然有点老套,但它可以完成工作。希望对你有用。

    【讨论】:

    • 谢谢,这几乎可以完美运行!我只需要在循环之后插入sub.finish()total.finish(),否则它不会“关闭”。你能把它插入你的代码吗?
    • 我添加了它。不过,奇怪的是,当您达到最大值时,我的本地版本的进度条会自行完成。也许版本不同?哦,好吧,你成功了,这才是最重要的。
    • 嗯,这对我不起作用,但我正在使用with as 声明。也许按照常规方式,使用start()finish(),它可以工作
    • 任何人都可以在 jupyter notebook 中添加相同的食谱吗?这种 up/down hack 在那里不起作用,kist 会生成新的 bar 副本。
    【解决方案4】:

    使用enlighten:

    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()
    

    【讨论】:

      【解决方案5】:

      在游戏中有点晚了,但这是一个只使用 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,  
      

      【讨论】:

        【解决方案6】:

        this answer 的启发,我还尝试了enlighten python 库并编写了我的简单辅助函数pit() 用于将迭代器包装在for循环(代码顶部)中,并提供了使用示例(代码底部)加上live终端截屏。

        与链接答案的主要区别在于pit() 允许在 for 循环中使用来包装迭代器,而不是使用手动 .update() 方法,englighten 中缺少这种迭代器包装功能,这就是我决定的原因实现我自己的。

        正如在已接受的答案中看到的那样,其他著名的进度条库(例如 tqdm)已经具有在 for 循环中包装迭代器以及在嵌套循环中包装多个进度条的功能。

        在 Linux 和 Windows 中都可以使用彩色。

        Try it online!

        # 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):

        【讨论】:

          【解决方案7】:

          这可以通过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
          

          【讨论】:

            【解决方案8】:

            我为 Python3.6+ 绘制了一个独立的、简单的进度条。没有 tqdm,没有其他依赖项,没有 hack。

            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='ᗦᗠᗦᗠᗦᗠ'

            【讨论】:

            • 不错的解决方案,赞成!如果您修改 myprogress() 函数的代码以使其真正没有依赖关系,那就太好了。现在它依赖于 Numpy,因为它使用 np.where()。在纯 Python 中实现这个 np.where() 非常容易。如果你的进度条只有纯 python 代码,没有外部模块,那就太好了。
            • @Arty 全部修复,谢谢!
            • 感谢修复!我决定使用您非常简单的myprogress() 创建包装函数pbiter(),它可以包装任何可迭代对象,并可用于自动显示任何嵌套循环中的进度。见my answer,它有你和我的完整代码以及细节和ASCII视频作为例子。
            • 感谢@dominecf 提供此解决方案。投票赞成。我喜欢没有依赖关系的解决方案我们可以让进度条一个在另一个之上吗?
            • 是的,@Kenny 的解决方案似乎提供了多列重写。我不确定它与所有终端的兼容性,但它看起来不错。只需修改上面的print 命令即可得到你想要的格式。
            【解决方案9】:

            这是一个显示外循环和内循环进度的简单方法:

            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]                                                                                                                                      
            

            【讨论】:

              【解决方案10】:

              受@dominecf 的answer 的简单性启发,为了好玩,我实现了一个辅助包装函数pbiter(),可以在循环中使用它来显示任何迭代的进度。 pbiter() 使用@dominecf 对myprogress() 的实现。

              不要过多评价这个答案,它只是为了在纯Python中从头开始实现进步的黑客乐趣,这个答案并不意味着在任何生产环境中使用,使用tqdmenlighten模块在真正的进步应用。

              请参阅我的另一个 answer 到同一个问题,该答案显示了如何使用 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。

              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 视频输出(另见 asciinema video page):

              如果由于某种原因你没有循环并且仍然想使用我的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)
              

              换句话说,您可以在任何代码位置以任何顺序手动创建和推进进度条。

              【讨论】:

              • 对我来说有点难读,但很好。与 OP 的问题相关:我猜嵌套迭代使 99% 的用例用于多个进度条。
              • @dominecf 感谢您的支持! :) 如果由于某种原因您没有循环,那么您仍然可以使用我的解决方案,只需创建 3 个没有循环的进度条,如 a = pbiter(range(5)); b = pbiter(range(4)); c = pbiter(range(3)),然后以任何顺序手动推进它们,并在代码的任何位置通过常规 @ 987654344@,点赞关注next(a); next(b); next(c); next(c); next(b); next(c)
              【解决方案11】:

              这里曾经是@yurenchen 的回答(已被删除),广告rich 库,它有进度条例程描述here in docs

              python -m pip install rich可以安装丰富的库。

              显示三个不同颜色的进度条堆栈的最小示例是:

              Try it online!

              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):

              【讨论】:

                猜你喜欢
                • 2017-12-03
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-03-10
                • 1970-01-01
                相关资源
                最近更新 更多