python-progressbar:基于 Python 的文本进度条库项目

Progressbar 2 - A progress bar for Python 2 and Python 3 - "pip install progressbar2"

分支2Tags112
文件最后提交记录最后更新时间
16 天前
16 天前
16 天前
16 天前
16 天前
16 天前
1 个月前
18 天前
1 个月前
1 个月前
18 天前
1 个月前
18 天前
18 天前
1 个月前
16 天前
16 天前
16 天前
16 天前
17 天前
16 天前
16 天前
16 天前

progressbar2

Python 中最快的进度条库,自 2012 年起持续维护。

test status coverage status PyPI version PyPI downloads per month supported Python versions license

为一个高速循环包裹进度条所带来的开销,有时甚至超过循环本身——因此,单次迭代的开销才是关键指标。本次竞赛重现了同一个 1,000,000 次迭代循环的实测墙钟时间,并用各库的默认设置进行包裹:

基准竞赛:进度条按各库完成相同百万次迭代循环的实测时间比例填充,progressbar2 率先完成

单次迭代开销 同一循环,墙钟时间
progressbar2[fast] 3.6 ns 9.2 ms
rich 18.2 ns 23.8 ms
progressbar2 26.0 ns 31.5 ms
tqdm 52.1 ns 57.6 ms
alive-progress 243.3 ns 248.9 ms
click 1829.6 ns 1835.2 ms

[fast] 一行对应 pip install 'progressbar2[fast]':它借助 speedups 包中的可选 C 迭代器,以原生方式计数,仅在进度条确实需要重绘时才回调 Python。普通安装则运行纯 Python 门控,开销为 26 ns,仍比 tqdm 轻一倍。导入开销也遵循同样的规律:import progressbar 仅需 1.5 ms,而 tqdm 需要 21.6 ms,rich 需要 45.6 ms——这对命令行工具来说,感知差异相当明显。

注意: 以上数据来自单台机器(Apple Silicon、CPython 3.13、输出至真实 pty),且由 benchmarks/bench.py 这一特定测试测得。你的环境数据会有所不同。click 虽已测量,但被排除在竞赛动画之外,因其 1.8 秒的总耗时会使其他所有跑道被压成一条线。可通过 python benchmarks/bench.py 复现,完整细节见 benchmarks/report.md

安装

pip install progressbar2           # pure Python
pip install 'progressbar2[fast]'   # adds the native accelerator

快速上手

常见情况下,只需在可迭代对象周围添加一行代码:

import time
import progressbar

for item in progressbar.progressbar(range(100), desc='Loading'):
    time.sleep(0.02)

文档中的每个示例都可以在页面中直接运行。点击任意代码块上的运行按钮即可,无需安装任何环境。

颜色、渐变与动态标记

进度条可以呈现随进度变化的色彩渐变、固定颜色,或动态标记。多个定制样式的进度条还能通过一个MultiBar共享同一终端界面:

三款样式各异的进度条:红至绿渐变、蓝至品红渐变,以及青色动态旋转指示器

"""Three styled bars at once: two color gradients and a fixed-color spinner.

`gradient_colors` shifts a bar's fill color as its percentage grows, so
the download bar sweeps red through gold to green and the render bar
sweeps sky blue into fuchsia. The scan bar has no percentage to sweep
(its length is unknown), so `fixed_colors` gives its animated marker one
unchanging cyan instead.
"""

import sys
import time

import progressbar
from progressbar.terminal import ColorGradient, colors
from progressbar.widgets import TFixedColors, TGradientColors

STEPS = 24

HEALTH = ColorGradient(colors.red, colors.gold1, colors.green)
NEON = ColorGradient(colors.deep_sky_blue1, colors.fuchsia)


def main() -> None:
    with progressbar.MultiBar(fd=sys.stdout) as multibar:
        multibar['download'] = progressbar.ProgressBar(
            max_value=STEPS,
            widgets=[
                progressbar.Percentage(),
                ' ',
                progressbar.Bar(
                    gradient_colors=TGradientColors(fg=HEALTH, bg=None),
                ),
            ],
        )
        multibar['render'] = progressbar.ProgressBar(
            max_value=STEPS,
            widgets=[
                progressbar.Percentage(),
                ' ',
                progressbar.Bar(
                    gradient_colors=TGradientColors(fg=NEON, bg=None),
                ),
            ],
        )
        multibar['scan'] = progressbar.ProgressBar(
            max_value=progressbar.UnknownLength,
            widgets=[
                progressbar.Bar(
                    marker=progressbar.AnimatedMarker(),
                    fixed_colors=TFixedColors(
                        fg_none=colors.cyan1, bg_none=None
                    ),
                ),
            ],
        )

        for step in range(STEPS):
            multibar['download'].update(step + 1)
            multibar['render'].update(max(0, step - 4))
            multibar['scan'].update(step + 1)
            # Longer than the bars' 0.05s update gate, so every step
            # lands as a visible redraw.
            time.sleep(0.1)

        multibar['render'].update(STEPS)
        for bar in multibar.values():
            bar.finish()


if __name__ == '__main__':
    main()

实时进度条上方的日志

程序在进度条运行期间打印的任何内容通常都会破坏进度条的显示。使用 redirect_stdout 后,输出内容会整齐地显示在进度条上方:

progressbar2 在移动的进度条上方展示清晰的日志输出

"""A build log printing above a progress bar without corrupting it."""

import time

import progressbar

STEPS = 24


def main() -> None:
    with progressbar.ProgressBar(
        max_value=STEPS,
        prefix='Build ',
        redirect_stdout=True,
    ) as bar:
        for step in range(STEPS):
            if step in {8, 16}:
                print(f'log: completed step {step}')
            bar.update(step + 1)
            # Longer than the bar's 0.05s update gate, so every step
            # lands as a visible redraw.
            time.sleep(0.1)


if __name__ == '__main__':
    main()

日志记录以相同的方式集成,详见 日志记录指南

同时显示多个进度条

MultiBar 可以布局任意数量的命名进度条,支持从任意线程更新它们,并在退出时等待所有进度条完成:

多个进度条在同一终端中同步更新

"""Two named bars progressing at different rates in one terminal."""

import sys
import time

import progressbar

STEPS = 24


def main() -> None:
    with progressbar.MultiBar(fd=sys.stdout) as multibar:
        build = multibar['build']
        test = multibar['test']
        build.max_value = STEPS
        test.max_value = STEPS
        for step in range(STEPS):
            build.update(step + 1)
            test.update(min(STEPS, max(0, round((step - 3) * 1.2))))
            # Longer than the bars' 0.05s update gate, so every step
            # lands as a visible redraw.
            time.sleep(0.1)

        # Reaching max_value doesn't finish a bar -- only finish() does.
        # A MultiBar waits for every bar to report finished() before its
        # context manager can exit, so without these calls the block
        # above would hang forever on exit.
        build.finish()
        test.finish()


if __name__ == '__main__':
    main()

并行执行

对一批项目运行某个函数通常意味着需要手动将执行器、结果收集和进度显示串联起来。而只需一次调用即可完成全部三项工作,无论是在线程、进程还是 asyncio 环境下:

import progressbar

results = progressbar.map(fetch, urls, workers=8)  # threads
results = progressbar.map(crunch, files, pool='process')  # processes
results = await progressbar.amap(fetch, urls)  # asyncio

# A progress-bar'd xargs -P:
progressbar.run('gzip -k {}', files, workers=4)

# An overall bar plus one bar per in-flight task:
progressbar.map(crunch, files, workers=4, bar='multi')

结果按输入顺序返回,imap/imap_unordered 则会以流式方式返回,而 gather 是带有进度条的 asyncio.gather 直接替代方案。下方动画在线程池中运行了八个任务,每个任务通过各自的进度条报告子进度:

一个总进度条,以及每个运行任务对应的单独进度条,由 progressbar.map 驱动

"""One call fans work out to threads and renders every bar for you.

`progressbar.map` runs `crunch` over the batch on a thread pool and
drives one overall bar plus a live bar per running task. The everyday
spelling is `bar='multi'`. Handing it a `MultiBar` instance instead, as
here, lets the finished overall bar stay on screen when the run ends.
Inside a task, `progressbar.current_task_bar()` hands back that task's
own bar so it can report sub-progress too.
"""

import sys
import time

import progressbar

FILES = [
    'alpha.dat',
    'bravo.dat',
    'charlie.dat',
    'delta.dat',
    'echo.dat',
    'foxtrot.dat',
    'golf.dat',
    'hotel.dat',
]


def crunch(path: str) -> str:
    # Deterministic, per-file amount of work: later files take longer,
    # so the bars visibly finish one after another.
    bar = progressbar.current_task_bar()
    for block in range(4 + FILES.index(path)):
        if bar is not None:
            bar.update(block + 1)
        time.sleep(0.05)
    return path


def main() -> None:
    multibar = progressbar.MultiBar(fd=sys.stdout, sort_reverse=False)
    results = progressbar.map(crunch, FILES, workers=8, bar=multibar)
    assert results == FILES
    # One more beat before the interpreter exits, so the finished
    # overall bar's final redraw reaches the terminal.
    time.sleep(0.1)


if __name__ == '__main__':
    main()

错误、超时、进程池以及装饰器形式等内容,详见并行执行指南

在命令行中替代 pv

安装该包的同时也会安装一个 progressbar 命令,它是经典 Unix 工具 pv 的 Python 实现:该命令将输入复制到输出,并在标准错误输出上绘制传输进度,因此可以无缝嵌入管道流程:

# File to file, with percentage, timer, ETA, rate and byte count:
progressbar --progress --timer --eta --rate --bytes data.bin -o copy.bin

# In a pipeline, data on stdout and progress on stderr:
tar cf - src/ | progressbar --bytes --rate > backup.tar

the progressbar command copying a file with percentage, timer, ETA, rate and byte count displays

该动画展示的正是这一传输过程,由 docs/examples/readme/cli.py 在进程内驱动:以 2 MiB/s 的限速复制 4 MiB 数据。限速(--rate-limit)、 行计数(--line-mode)以及大部分其他 pv 参数均已支持, progressbar --help 可查看完整列表。

未知长度与动画进度条

即使没有已知的总长度,任务仍能获得实用的进度条——以动画标记 配合计数器来替代百分比:

unknown length progress with an animated marker

"""A bar for work whose total is not known up front."""

import time

import progressbar


def main() -> None:
    with progressbar.ProgressBar(
        max_value=progressbar.UnknownLength,
    ) as bar:
        for value in range(0, 120, 10):
            bar.update(value)
            # Longer than the bar's 0.05s update gate, so every step
            # lands as a visible redraw.
            time.sleep(0.1)


if __name__ == '__main__':
    main()

关键时刻的乏味

上述功能是光鲜亮丽的一面。而另一面,则是这个包自 2012 年以来长盛不衰的原因:

  • 自 2012 年起持续维护,在 PyPI 上发布了 111 个版本;更早之前,自 2008 年起作为最初的 progressbar 库存在,而 progressbar2 至今仍可作为其直接替代品。
  • 1133 个测试,分支覆盖率 100%,并在每次提交的 CI 中强制执行。
  • 完全类型标注(PEP 561 py.typed),让你的类型检查器看到真实的函数签名。
  • 支持 Python 3.10 至 3.14,外加 PyPy,3.15 的预发布版本也已纳入 CI 测试。
  • 本页面上的动画演示均来自真实捕获的终端输出,并在 CI 中逐字节重新验证,确保你所见即库实际所绘。

已知终端注意事项

  • JetBrains IDE 需要启用 “Enable terminal in output console” 才能支持 MultiBar 等高级终端行为。
  • IDLE 不支持终端进度条。
  • Jupyter 会缓冲 stdout,因此当输出出现延迟时,请调用 sys.stdout.flush()

项目历史

progressbar2 基于旧的 Python progressbar 包,该包最初发布在现已关闭的 Google Code 上。由于该项目已被其开发者完全弃养,且开发者未回复邮件,我决定fork 该包。

该包至今仍与原版 progressbar 包保持向后兼容,因此你可以将其用作现有项目的直接替代品。

链接

项目介绍

Progressbar 2 - A progress bar for Python 2 and Python 3 - "pip install progressbar2"

定制我的领域