🚀 原文地址:https://rich.readthedocs.io/en/latest/progress.html

Rich 可以显示有关时间运行任务或者文件复制等进度的持续更新信息,显示的信息是可配置的,默认情况下将显示任务的描述信息、进度条、完成百分比,以及估计剩余时间。

Rich 进度条显示支持多个任务,每个任务都会有一个进度条信息,我们可以使用这个特性来跟踪线程或进程中发生的并发任务。使用以下命令来查看进度条示例:

  1. $ python -m rich.progress

:::danger 🎈 注意
——————————
如果在 Jupyter Notebook 中使用进度条,会关闭自动刷新功能。我们需要显示地调用refresh函数或者在调用update方法时设置refresh=True,或者使用track函数,它会自动刷新每个循环。 :::

1. 基本使用

最基本的用法是调用track函数,它将从序列中产生值,并在每次迭代中更新进度信息。track函数主要有以下参数,除sequence参数外其余都是可选参数:

  • sequence:序列,例如列表或者范围对象
  • description:正在处理的作业的描述,默认为Working。值得注意的是,我们可以在描述中[red][green]来改变进度条文本描述的颜色,如果你的Terminal 支持 Emoji 的话,也可以使用 Emoji。
  • total:总共的步数,默认是序列的长度
  • auto_refresh:自动刷新,默认为True,设置为False后将会每次迭代后强制刷新
  • console:所写入的控制台,默认为创建内部控制台实例
  • transient:在退出时清除进度条,默认为False
  • get_time:获取当前时间可调用对象,或者设置为None将使用Console.get_time,默认为None
  • refresh_per_second:每秒刷新进度信息的次数,默认为 10
  • style:进度条背景样式,默认为bar.back
  • complete_style:进度条完成时的背景样式,默认为bar.complete
  • finished_style:进度条结束时的背景样式,默认为bar.done
  • pulse_style:脉冲条的样式,默认为bar.pulse
  • update_period:调用update函数之间最短时间间隔(以秒为单位),默认为 0.1
  1. import time
  2. from rich.progress import track
  3. for n in track(range(10), description="[red]Loading..."):
  4. time.sleep(1)

如果你使用的是 PyCharm IDE,那么点击 RUN 脚本是无法看到效果的,需要在左下角 RUN 栏中点击设置,勾选上 Emulate terminal in the output console,才可以看到正确的效果,除此之外的方法就是使用 Terminal 来直接运行脚本。
image.png

2. 高级用法

2.1 多个任务进度条

如果需要显示多个任务,或者希望配置进度条的列,我们可以直接使用Progress类。一旦我们已经构建了Progress对象,使用add_task函数添加任务,并使用update函数更新进度条。Progress类旨在用作上下文管理器,它将自动启动和停止进度条显示。

Progress类具有以下初始化参数,如果希望进度条拥有更多样式,请参考 2.8 部分

  • console:所写入的控制台,默认为创建内部控制台实例
  • auto_refresh:自动刷新,默认为True,设置为False后将会每次迭代后强制刷新
  • refresh_per_second:每秒刷新进度信息的次数,默认为 10
  • speed_estimate_period
  • transient:在退出时清除进度条,默认为False
  • redirect_stdout:开启 stdeout 的重定向,默认为True
  • redirect_stderr:开启 stderr 的重定向,默认为True
  • get_time:获取当前时间可调用对象,或者设置为None将使用Console.get_time,默认为None
  • disable:关闭进度条显示,默认为False
  • expand:扩展任务表以适应宽度,默认为False

add_task方法具备可选参数:

  • description:任务描述信息
  • start:立即开启任务(以估计经过的时间),默认为True。如果设置为False,我们需要手动调用start,此外该设置下进度条有光晕效果。
  • total:进度中的总步数,默认为 100
  • completed:到目前为止完成的步数,默认为 0
  • visible:设置进度条可见标识
  • **fields

在我的日常工作中,使用多个任务进度条一般会有以下 2 种情况:

  • 多个任务并行,每个进度条更新进度不一样
  • 嵌套任务,外层是一个任务进度条,内部还有一个进度条,只有内部进度条结束后,外部进度条更新一次并进入下个迭代,内部进度条重新开启

    1)多个任务并行

    下面这个示例,我们先初始化了一个Progress实例,并通过add_task添加了 3 个任务,这里的total参数和之前介绍的一样,即达到 100% 进度所需要总共的步数。这里解释一下步骤的含义,它是应用程序所做有意义的任何事情,例如文件读取的字节数或者处理的图像数等。

值得一提的是,这里有个advance参数,对于每个任务,会用total/advance重新得到该任务进度条结束所需的步数,即对于第一个任务需要 10 步,第二个任务需要 50 步,第三个任务则需要 200 步。相对应地,就可以得出每次循环迭代下各自任务更新进度条的比例,第一个任务为 10%,第二个任务为 2%,第三个任务为 0.5(即 2 次循环更新 1%)。

最终下面这个示例进度条显示为 task1 为 100%,task2 为 20%,task3 为 5%。

  1. import time
  2. from rich.progress import Progress
  3. with Progress() as progress:
  4. task1 = progress.add_task("[red]Downloading...", total=10)
  5. task2 = progress.add_task("[green]Processing...", total=100)
  6. task3 = progress.add_task("[cyan]Cooking...", total=1000)
  7. for i in range(10):
  8. progress.update(task1, advance=1)
  9. progress.update(task2, advance=2)
  10. progress.update(task3, advance=5)
  11. time.sleep(1)

如果希望整个进度条完成时结束,则可以使用以下代码:

  1. while not progress.finished:
  2. progress.update(task1, advance=1)
  3. progress.update(task2, advance=2)
  4. progress.update(task3, advance=5)
  5. time.sleep(1)

有时我们的序列长度并不一定是整数,我们希望每次访问一次列表中元素进行更新一次,可以直接将advance设置为 1。

2)嵌套任务

嵌套任务则是指外部进度条与内部进度条依次更新,两者有依赖关系,只有内部进度条更新完了,外部进度条才会更新一次:

  1. import time
  2. from rich.progress import Progress
  3. with Progress() as progress:
  4. task1 = progress.add_task("[red]Master Progress Bar...", total=2)
  5. for i in range(2):
  6. task2 = progress.add_task(f"[green]Sub Progress Bar: {i + 1}...", total=10)
  7. for j in range(10):
  8. progress.update(task2, advance=1)
  9. time.sleep(1)
  10. time.sleep(1)
  11. progress.update(task1, advance=1)

2.2 更新任务

当调用add_task函数时,我们将得到一个 Task ID。当完成某些工作或信息发生更改时,将 Task ID 传给update函数。通常,在每次完成一个步骤之后需要进行更新completed值,我们可以通过直接更新completed参数值或者设置advance值(advance的值将会添加道当前completed值上)来完成。

所以上面我们的示例,也可以采用更新completed值来实现对应功能:

  1. import time
  2. from rich.progress import Progress
  3. with Progress() as progress:
  4. task = progress.add_task("[red]Master Progress Bar...", total=7)
  5. for i in range(7):
  6. progress.update(task, completed=i + 1)

update方法搜集与任务相关联的关键字参数,使用它来提供在进度条显示的任何其他信息。此外,update方法还有以下参数可进行设置:

  • task_id:任务 ID
  • total:如果该参数不是None,将会更新task.total
  • completed:如果该参数不是None,将会更新task.completed值。如上所述,completed / total的值即为总步数中已经完成步数的比例
  • advance:如果该参数不是None,将advance值添加到task.completed
  • description:如果该参数不是None,改变任务的描述信息。例如下面这个示例,我们可以在描述中添加序列总长度,以及当前占整个序列的epoch值
  • visible:设置进度条可见标识
  • refresh:强制刷新进度条信息,默认为False
  • **fields:所需要渲染的其他字段
  1. import time
  2. from rich.progress import Progress
  3. with Progress() as progress:
  4. p, total = 0, 10
  5. description = f"[red]Loading ({p}/{total})..."
  6. task = progress.add_task(description.format(), total=total)
  7. for p in range(1, total + 1):
  8. description = f"[red]Loading ({p}/{total})..."
  9. progress.update(task, completed=p, description=description)
  10. time.sleep(1)

2.3 隐藏任务

我们可以通过更新任务的visible值来显示/隐藏任务,默认情况下,任务是可见的,但我们也可以通过调用add_task函数并设置visible=False来添加不可见任务。

2.4 瞬态进度

通常,当我们退出进度条上下文管理器(或者通过调用stop方法)时,最后刷新的显示仍保留在终端中,光标位于下一行。如果我们希望进度条在退出时消失,从而使得终端中的输出更简洁,可以在构造Progress构造函数中设置transient=True

  1. with Progress(transient=True) as progress:
  2. task = progress.add_task("Working", total=100)
  3. do_work(task)

2.5 不确定进度

添加任务时,它会自动启用,这意味着进度条会在 0% 处开始,剩余时间将从当前时间计算。如果在开始更新进度之前有很长的延迟,这可能无法正常工作,例如我们需要等待服务器的响应或计算目录中的文件。在这些情况下,我们可以调用add_task函数设置start=False,它将显示一个脉冲动画,让用户知道某些东西正在工作,这种情况称为不确定进度条。

当我们知道步数时,可以调用start_task方法,它将在 0% 处显示进度条,然后像正常方式调用update方法。

2.6 自动刷新

默认情况下,进度条信息每秒刷新 10 次。当然,我们可以使用Progress构造器上的refresh_per_second参数设置刷新率,如果更新不那么频繁,最好是将其设置为低于 10。

如果更新不是很频繁,我们可能希望完全禁用自动刷新,可以通过在构造函数上设置auto_refresh=False来实现。如果关闭自动刷新,则需要在更新任务后手动调用refresh方法。

2.7 扩展

进度条将仅使用显示任务信息所需的终端宽度,如果在Progress构造函数上设置了expand参数,那么 Rich 会将进度显示拉伸到完整的可用宽度。

2.8 列

我们可以使用Progress构造函数的参数自定义进度条显示的列,这些列被指定为格式字符串或者ProgressColumn对象。

格式字符串将使用单个task值(指代的是Task实例),例如"{task.description}"将在列中显示任务描述信息,"{task.completed} of {task.total}"将显示总步数中已经成的步数。

常用的Column有:

  • BarColumn:显示栏
  • TextColumn:显示文本
  • TimeElapsedColumn:显示经过的时间
  • TimeRemainingColumn:显示估计的剩余时间
  • FileSizeColumn:以文件大小显示进度
  • TotalFileSizeColumn:显示总文件大小
  • DownloadColumn:显示下载进度
  • TransferSpeedColumn:显示传输速度
  • SpinnerColumn:显示Spinner动画
  • RenderableColumn:在列中显示任意 Rich 可渲染对象

以下示例是使用各种Column自定义进度条,最终展示效果如下图所示
image.png

  1. import time
  2. from rich.progress import Progress, BarColumn, SpinnerColumn, TimeRemainingColumn, TimeElapsedColumn
  3. with Progress(
  4. "[progress.description]{task.description}({task.completed}/{task.total})",
  5. SpinnerColumn(finished_text="[green]✔"),
  6. BarColumn(),
  7. "[progress.percentage]{task.percentage:>3.2f}%",
  8. "[yellow]⏱",
  9. TimeElapsedColumn(),
  10. "[cyan]⏳",
  11. TimeRemainingColumn()) as progress:
  12. description = "[red]Loading"
  13. task = progress.add_task(description, total=1000)
  14. for p in range(1, 1001):
  15. if p != 1000:
  16. description = "[red]Loading"
  17. else:
  18. description = "[green]Finished"
  19. progress.update(task, completed=p, description=description)
  20. time.sleep(0.02)

如果 Rich 所提供的Column无法满足你的需求,你也可以通过继承ProgressColumn类进行扩展,并像上述方式一样进行使用。

2.9 表格列

Rich 为Progress实例任务构建了一个Table对象,我们可以通过在Column构造器中指定table_column参数来自定义任务表列的创建方式,该参数的值应该是Column实例。

以下示例演示了一个进度条,其中描述占终端的宽度的 1/3(也就是Column(ratio=1)),进度条则占了 2/3,

  1. import time
  2. from rich.table import Column
  3. from rich.progress import Progress, BarColumn, TextColumn
  4. text_column = TextColumn("{task.description}", table_column=Column(ratio=1))
  5. bar_column = BarColumn(bar_width=None, table_column=Column(ratio=2))
  6. progress = Progress(text_column, bar_column, expand=True)
  7. with progress:
  8. for n in progress.track(range(100)):
  9. progress.print(n)
  10. time.sleep(0.1)

2.10 打印/记录日志

Progress类将创建一个内部控制台对象,我们可以用过progress.console访问该对象。如果希望打印或登录到此控制台,输出将显示在进度条上方。下面示例中可以直接用progress.print代替progress.console.print

  1. import time
  2. from rich.progress import Progress
  3. with Progress() as progress:
  4. task = progress.add_task("twiddling thumbs", total=10)
  5. for job in range(10):
  6. progress.console.print(f"Working on job #{job}")
  7. time.sleep(1)
  8. progress.advance(task)

同样,我们也可以将另外的Console对象传递给Progress类进行使用:

  1. from my_project import my_console
  2. with Progress(console=my_console) as progress:
  3. my_console.print("[bold blue]Starting work!")
  4. do_work(progress)

2.11 重定向标准输出/标准错误

为了避免破环进度条显示的视觉效果,Rich 将重定向stdoutstderr,以便我们可以使用内置的print语句。此功能默认是开启的,但可以通过将redirect_stdoutredirect_stderr设置为False

2.12 自定义

如果Progress类在进度条显示方面没有提供我们所需要的内容,还可以通过覆写get_renderables方法来达到想要的效果。例如,以下类将在进度条显示周围呈现一个面板:

  1. import time
  2. from rich.panel import Panel
  3. from rich.progress import Progress
  4. class FrameProgress(Progress):
  5. def get_renderables(self):
  6. yield Panel(self.make_tasks_table(self.tasks), expand=False)
  7. with FrameProgress() as progress:
  8. task1 = progress.add_task("[red]Master Progress Bar...", total=10)
  9. for i in range(5):
  10. task2 = progress.add_task(f"[green]Sub Progress Bar: {i + 1}...", total=10)
  11. for j in range(10):
  12. progress.update(task2, advance=1)
  13. time.sleep(1)
  14. time.sleep(1)
  15. progress.update(task1, completed=1)

3. 多重进度

对于单个Progress实例,每个任务不能有不同的列。但是,你可以在实时显示中拥有任意数量的进度实例。

4. 示例

Rich 官方提供了一个示例脚本来演示进度条显示的实际应用,该脚本可以下载多个带有进度条、传输速度和文件大小的并发文件。