Python进度条

548

当我的脚本执行一些可能需要时间的任务时,如何使用进度条呢?

例如,在完成某个函数并返回True时需要一定的时间。我如何在函数执行期间显示进度条呢?

请注意,我需要实时显示进度条,所以我不知道该怎么办。我需要为此使用thread吗?我不知道。

目前我在函数执行期间没有任何打印输出,但一个进度条会很好。我更关心的是从代码角度如何实现这一点。


你是使用GUI工具包还是仅限于CLI? - Bobby
使用GUI可以做到这一点,但我对CLI部分感兴趣。无论如何,我可以使用第三方库。 - user225312
4
可能是重复的问题,与“在控制台中的文本进度条”有关。请注意,虽然这个问题发布时间早于另一个问题三天,但是链接的那个问题更常被查看。 - Greenstick
这是一个在Jupyter Notebook中的解决方案:https://mikulskibartosz.name/how-to-display-a-progress-bar-in-jupyter-notebook-47bd4c2944bf - Steven C. Howell
我发布了一种新型进度条,你可以打印它,查看吞吐量和预计时间,甚至可以暂停它,除此之外还有非常酷的动画效果!请查看:https://github.com/rsalmei/alive-progress alive-progress - rsalmei
我没有循环,只有一个写入命令。在这种情况下,是否可能?with open(path2file, 'wb+') as f: # with open(path2file, 'w+') as f: f.write(data.read()) - Charlie Parker
47个回答

731

使用 tqdm (conda install tqdm 或者 pip install tqdm),你可以在一秒钟内为你的循环添加进度条:

from time import sleep
from tqdm import tqdm
for i in tqdm(range(10)):
    sleep(3)

 60%|██████    | 6/10 [00:18<00:12,  0.33 it/s]

此外,还有一个笔记本版本

from tqdm.notebook import tqdm
for i in tqdm(range(100)):
    sleep(3)

你可以使用 tqdm.auto 代替 tqdm.notebook 来在终端和笔记本中工作。

tqdm.contrib 包含一些辅助函数,例如enumeratemapzip。在tqdm.contrib.concurrent 中有并行映射。

甚至可以使用tqdm.contrib.telegramtqdm.contrib.discord在从jupyter笔记本断开连接后将进度发送到您的手机。

演示使用tqdm.contrib.telegram在Telegram移动应用程序中显示进度条的输出的GIF


18
这是我找到的唯一可与终端、qtconsole和notebook配合使用的解决方案。 - Ivelin
3
它可以适用于任何可迭代对象吗?我尝试将其应用于字符串列表时遇到了问题。 - Josh Usre
3
@JoshUsre 是的,它可以与任何可迭代对象一起使用,目前我还没有看到它无法处理的可迭代对象。但是,显示剩余时间(ETA)需要可迭代对象具有__len__属性,或者用户必须向tqdm提供total参数。否则,进度条将正常工作但没有ETA。 - gaborous
9
为什么这不是最受欢迎的答案?这个简单的解决方案既适用于终端又适用于Jupyter笔记本电脑,而与最佳答案不同。 - Ébe Isaac
6
在 Jupyter Notebook 中运行,请使用 from tqdm import tqdm_notebook as tqdm。否则请勿将其写在一行中。 - Jacques MALAPRADE
显示剩余11条评论

254
使用alive-progress,这是有史以来最酷的进度条!只需pip install alive-progress,你就可以开始了!

GIF showing an example of alive-progress

要有效地使用任何进度条,即获得完成百分比和预计剩余时间,您需要能够告诉它总项目数。然后,alive-progress将跟踪您当前的处理位置以及需要多长时间!
如果您无法估计总数,不用担心,alive-progress仍然可以工作。
要使用它,您可以直接驱动alive-progress的进度条:
def compute():
    with alive_bar(1000) as bar:  # your expected total
        for item in items:        # the original loop
            print(item)           # your actual processing here
            bar()                 # call `bar()` at the end


compute()

或者,如果您希望保持代码隔离,只需在处理代码中插入 yield (以标记项目已处理),然后像这样驱动 alive-progress 的进度条:

def compute():
    for item in items:
        print(item)
        yield                  # simply insert this :)


with alive_bar(1000) as bar:
    for i in compute():
        bar()

无论哪种方式,您都会得到一个令人赞叹且有动感的进度条!
|█████████████▎                      | ▅▃▁ 321/1000 [32%] in 8s (40.1/s, eta: 16s)

它支持很多高级选项,开箱即用! 一些高级选项

免责声明:我是《alive-progress》的自豪作者,在 GitHub 上获得了4.6K+⭐️的赞!
阅读文档https://github.com/rsalmei/alive-progress以了解所有高级功能。

请看一下它在旋转器和进度条小部件中可以实现的更多动画效果: 显示各种 alive-progress 样式的 GIF

它还适用于 Jupyter Notebooks! 在 Jupyter Notebooks 中显示 alive-progress 的 GIF

您甚至可以设计自己的动画! 显示动画设计师的 GIF


8
好的!Jupyter笔记本支持进展如何? - blupp
在项目中开一个问题,我可以在那里帮助你。 - rsalmei
我没有循环,只有一个写入命令。在这种情况下,这是否可能? with open(path2file, 'wb+') as f: # with open(path2file, 'w+') as f: f.write(data.read()) - Charlie Parker
是的,@CharlieParker,你也可以使用它!虽然你没有循环,但有很多字节,只需要多次操作即可...我建议你将工作分成1KB、8KB或甚至1MB大小的块,并使用每个块的写入来驱动进度条! - rsalmei
1
谢谢,@ruloweb。您可以根据需要增加数量,只需在迭代结束时调用 bar(size) 即可。 - rsalmei
显示剩余2条评论

231

有特定的库(像这里的这个),但也许一些非常简单的东西也可以:

import time
import sys

toolbar_width = 40

# setup toolbar
sys.stdout.write("[%s]" % (" " * toolbar_width))
sys.stdout.flush()
sys.stdout.write("\b" * (toolbar_width+1)) # return to start of line, after '['

for i in range(toolbar_width):
    time.sleep(0.1) # do real work here
    # update the bar
    sys.stdout.write("-")
    sys.stdout.flush()

sys.stdout.write("]\n") # this ends the progress bar
注意:progressbar2progressbar的分支,后者已经多年未得到维护。

21
这无法适用于许多步骤... https://pypi.python.org/pypi/progress 更容易使用。 - m13r
8
我尝试了这段代码,但是它报错了:“NameError: name 'xrange' is not defined”。我是否缺少某个模块? - Mushroom Man
8
你使用的Python版本是哪个? Python 2中的 xrange 在Python 3中等同于 range - quapka
12
这不应该是最佳答案。至少对我来说,另一个答案(使用tqdm)更好。 - Florian
7
Python 3 中的穷人进度条:print('■', end='', flush=True) - PatrickT
显示剩余7条评论

173
没有外部包。一个现成的代码片段。
您可以自定义进度条符号“#”,进度条大小,文本前缀等。
Python 3.6+(f-string)带有剩余时间估计。
import sys
import time

def progressbar(it, prefix="", size=60, out=sys.stdout): # Python3.6+
    count = len(it)
    start = time.time()
    def show(j):
        x = int(size*j/count)
        remaining = ((time.time() - start) / j) * (count - j)
        
        mins, sec = divmod(remaining, 60)
        time_str = f"{int(mins):02}:{sec:05.2f}"
        
        print(f"{prefix}[{u'█'*x}{('.'*(size-x))}] {j}/{count} Est wait {time_str}", end='\r', file=out, flush=True)
        
    for i, item in enumerate(it):
        yield item
        show(i+1)
    print("\n", flush=True, file=out)

[████████████████████████████.........................] 24/50 Est wait 00:05.38

使用方法:

import time    
for i in progressbar(range(15), "Computing: ", 40):
    time.sleep(0.1) # any code you need

enter image description here


  • 不需要第二个线程。一些解决方案/包需要。

  • 适用于任何可迭代对象,也就是任何可以使用len()的对象。例如,一个list,一个dict,例如['a', 'b', 'c' ... 'g']

  • 适用于生成器,只需要用list()将其包装起来。例如for i in progressbar(list(your_generator), "Computing: ", 40): 除非工作是在生成器中完成。在这种情况下,您需要另一种解决方案(如tqdm)

您还可以通过将out更改为sys.stderr来更改输出,例如。


Python 3.3+

import sys
def progressbar(it, prefix="", size=60, out=sys.stdout): # Python3.3+
    count = len(it)
    def show(j):
        x = int(size*j/count)
        print("{}[{}{}] {}/{}".format(prefix, "#"*x, "."*(size-x), j, count), 
                end='\r', file=out, flush=True)
    show(0)
    for i, item in enumerate(it):
        yield item
        show(i+1)
    print("\n", flush=True, file=out)

Python 2(旧版本代码)

import sys
def progressbar(it, prefix="", size=60, out=sys.stdout):
    count = len(it)
    def show(j):
        x = int(size*j/count)
        out.write("%s[%s%s] %i/%i\r" % (prefix, u"#"*x, "."*(size-x), j, count))
        out.flush()        
    show(0)
    for i, item in enumerate(it):
        yield item
        show(i+1)
    out.write("\n")
    out.flush()

2
我喜欢这个解决方案,生成器将会抛出以下错误:TypeError: object of type 'generator' has no len() - jabellcu
3
希望之前看到这个评论,白费时间思考如何将它与生成器配合使用。我必须说,将其转换为列表可能对大型对象没有帮助,因为生成器的优点就失去了。(在我的情况下,我正在从PDF中读取页面,而且我不想将所有页面加载到内存中)。我欣赏这种简单方法,而不是添加一个进度条库。 - rainversion_3
3
将生成器包装为列表似乎确实失去了重点。如果所有工作都在生成器中完成,那么进度条将无法显示进度。 (例如,tqdm通过不显示百分比来处理这个问题,除非您告诉它总数)关于线程问题的评论可能不是100%准确的。在jupyter笔记本中,第二个线程不会成为问题。写入两个不同的输出(stdout和stderr)才是问题所在。 - de1
2
我制作了一个“更好”的版本,它用一个填充整个字符空间的Unicode字符替换了#字符。这是我制作的一个要点:https://gist.github.com/ChesterChowWOV/2b35c551b339adbf459363322aac5b4b - ChesterWOV
1
我更喜欢这些字符:█和▒。 - SurveyLoophole
显示剩余8条评论

106

以上建议都很好,但我认为大多数人只想要一个现成的解决方案,不依赖外部包,同时也可重复使用。

我汲取了所有建议中最好的要点,并将其制作成一个函数,附带测试用例。

要使用它,只需复制“def update_progress(progress)”下面的行,但不包括测试脚本。别忘了导入sys。每当需要显示或更新进度条时,请调用此函数。

该方法通过直接向控制台发送“\r”符号来将光标移回开头。Python中的“print”无法识别上述符号以实现此目的,因此我们需要 'sys'。

import time, sys

# update_progress() : Displays or updates a console progress bar
## Accepts a float between 0 and 1. Any int will be converted to a float.
## A value under 0 represents a 'halt'.
## A value at 1 or bigger represents 100%
def update_progress(progress):
    barLength = 10 # Modify this to change the length of the progress bar
    status = ""
    if isinstance(progress, int):
        progress = float(progress)
    if not isinstance(progress, float):
        progress = 0
        status = "error: progress var must be float\r\n"
    if progress < 0:
        progress = 0
        status = "Halt...\r\n"
    if progress >= 1:
        progress = 1
        status = "Done...\r\n"
    block = int(round(barLength*progress))
    text = "\rPercent: [{0}] {1}% {2}".format( "#"*block + "-"*(barLength-block), progress*100, status)
    sys.stdout.write(text)
    sys.stdout.flush()


# update_progress test script
print "progress : 'hello'"
update_progress("hello")
time.sleep(1)

print "progress : 3"
update_progress(3)
time.sleep(1)

print "progress : [23]"
update_progress([23])
time.sleep(1)

print ""
print "progress : -10"
update_progress(-10)
time.sleep(2)

print ""
print "progress : 10"
update_progress(10)
time.sleep(2)

print ""
print "progress : 0->1"
for i in range(101):
    time.sleep(0.1)
    update_progress(i/100.0)

print ""
print "Test completed"
time.sleep(10)

这是测试脚本结果显示的内容(最后一个进度条会动画显示):

progress : 'hello'
Percent: [----------] 0% error: progress var must be float
progress : 3
Percent: [##########] 100% Done...
progress : [23]
Percent: [----------] 0% error: progress var must be float

progress : -10
Percent: [----------] 0% Halt...

progress : 10
Percent: [##########] 100% Done...

progress : 0->1
Percent: [##########] 100% Done...
Test completed

10
最后一个动画测试应该说“在范围(101)内”,而不是100,在99%处进度停止,从未显示完成。 - Nick Humrich
这是一个很好的答案!两个建议:1)您可以使用print(...,end ='')而不是调用stdout.write()+ stdout.flush()。2)如果您将\r放在字符串末尾而不是开头,则与其他控制台输出更加协调。 - Tim Sparkles
你如何使进度条每次更新时覆盖先前的内容,而不是在控制台中每次附加新行? - user5359531
@user5359531,请尝试下面的答案。 - imbr
我将进度条的格式更改为保留2位小数,如下所示:text = "\r百分比: [{0}] {1:.2f}% {2}".format( "#"*block + "-"*(barLength-block), progress*100, status) - Kory Gill

31

尝试使用https://pypi.python.org/pypi/progress来实现进度。

from progress.bar import Bar

bar = Bar('Processing', max=20)
for i in range(20):
    # Do some work
    bar.next()
bar.finish()

结果将会是以下这样的一个条形图:

Processing |#############                   | 42/100

刚试了一下,非常容易使用。只用了2分钟(包括pip安装进度),就能够让状态栏运行起来了。 - perelin
progress 可以制作出漂亮的进度条,但如果其他软件正在操作 stderr,它就会失败。很抱歉,我还没有调查清楚具体的问题。 - Arthur
它在我的Ubuntu控制台中为每个进度打印一行,例如,如果max = 20,则会打印20行...我该如何使其只打印一行? - L's World

26

对于一个类似的应用(在循环中跟踪进度),我只是使用了python-progressbar

他们的示例大致如下:

from progressbar import *               # just a simple progress bar


widgets = ['Test: ', Percentage(), ' ', Bar(marker='0',left='[',right=']'),
           ' ', ETA(), ' ', FileTransferSpeed()] #see docs for other options

pbar = ProgressBar(widgets=widgets, maxval=500)
pbar.start()

for i in range(100,500+1,50):
    # here do something long at each iteration
    pbar.update(i) #this adds a little symbol at each iteration
pbar.finish()
print

4
为了兼容Python 3,请尝试使用progressbar2包。上述代码将与其一起正常工作。 - d33tah
11
你刚才真的使用了 import * 吗? - eric
@Eric 我喜欢过危险的生活。 - Massagran

22

在这里搜索了一个类似的解决方案之后,我为自己的需求刚刚制作了一个简单的进度类。我想我可以将其发布出来。

from __future__ import print_function
import sys
import re


class ProgressBar(object):
    DEFAULT = 'Progress: %(bar)s %(percent)3d%%'
    FULL = '%(bar)s %(current)d/%(total)d (%(percent)3d%%) %(remaining)d to go'

    def __init__(self, total, width=40, fmt=DEFAULT, symbol='=',
                 output=sys.stderr):
        assert len(symbol) == 1

        self.total = total
        self.width = width
        self.symbol = symbol
        self.output = output
        self.fmt = re.sub(r'(?P<name>%\(.+?\))d',
            r'\g<name>%dd' % len(str(total)), fmt)

        self.current = 0

    def __call__(self):
        percent = self.current / float(self.total)
        size = int(self.width * percent)
        remaining = self.total - self.current
        bar = '[' + self.symbol * size + ' ' * (self.width - size) + ']'

        args = {
            'total': self.total,
            'bar': bar,
            'current': self.current,
            'percent': percent * 100,
            'remaining': remaining
        }
        print('\r' + self.fmt % args, file=self.output, end='')

    def done(self):
        self.current = self.total
        self()
        print('', file=self.output)

例子:

from time import sleep

progress = ProgressBar(80, fmt=ProgressBar.FULL)

for x in xrange(progress.total):
    progress.current += 1
    progress()
    sleep(0.1)
progress.done()

将打印以下内容:

[======== ] 17/80 ( 21%) 还有63个任务


3
太棒了,谢谢你的帮助。顺便提一下,在__call__的结尾处增加progress.current的递增操作可以更进一步地减少主代码与对象的交互。 - npit
这段代码简单、精炼且实用!谢谢! - Ian Rehwinkel

21

我喜欢 Brian Khuu的答案,因为它很简单且不需要外部包。我稍微修改了一下,所以在这里添加我的版本:

import sys
import time


def updt(total, progress):
    """
    Displays or updates a console progress bar.

    Original source: https://dev59.com/XXA75IYBdhLWcg3wrrNS#15860757
    """
    barLength, status = 20, ""
    progress = float(progress) / float(total)
    if progress >= 1.:
        progress, status = 1, "\r\n"
    block = int(round(barLength * progress))
    text = "\r[{}] {:.0f}% {}".format(
        "#" * block + "-" * (barLength - block), round(progress * 100, 0),
        status)
    sys.stdout.write(text)
    sys.stdout.flush()


runs = 300
for run_num in range(runs):
    time.sleep(.1)
    updt(runs, run_num + 1)

假设total >= progress,它获取运行的总数(total)和到目前为止已处理的运行数量(progress)。结果看起来像这样:

[#####---------------] 27%

20
你可以使用 tqdm:
from tqdm import tqdm

with tqdm(total=100, desc="Adding Users", bar_format="{l_bar}{bar} [ time left: {remaining} ]") as pbar:
    for i in range(100):
        time.sleep(3)
        pbar.update(1)

在这个例子中,进度条运行了5分钟,并且呈现如下:

Adding Users:   3%|█████▊                                     [ time left: 04:51 ]                                                                                                        
你可以根据自己的喜好进行更改和定制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接