Rich 可以显示有关时间运行任务或者文件复制等进度的持续更新信息,显示的信息是可配置的,默认情况下将显示任务的描述信息、进度条、完成百分比,以及估计剩余时间。
Rich 进度条显示支持多个任务,每个任务都会有一个进度条信息,我们可以使用这个特性来跟踪线程或进程中发生的并发任务。使用以下命令来查看进度条示例:
$ 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
import time
from rich.progress import track
for n in track(range(10), description="[red]Loading..."):
time.sleep(1)
如果你使用的是 PyCharm IDE,那么点击 RUN 脚本是无法看到效果的,需要在左下角 RUN 栏中点击设置,勾选上 Emulate terminal in the output console,才可以看到正确的效果,除此之外的方法就是使用 Terminal 来直接运行脚本。
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%。
import time
from rich.progress import Progress
with Progress() as progress:
task1 = progress.add_task("[red]Downloading...", total=10)
task2 = progress.add_task("[green]Processing...", total=100)
task3 = progress.add_task("[cyan]Cooking...", total=1000)
for i in range(10):
progress.update(task1, advance=1)
progress.update(task2, advance=2)
progress.update(task3, advance=5)
time.sleep(1)
如果希望整个进度条完成时结束,则可以使用以下代码:
while not progress.finished:
progress.update(task1, advance=1)
progress.update(task2, advance=2)
progress.update(task3, advance=5)
time.sleep(1)
有时我们的序列长度并不一定是整数,我们希望每次访问一次列表中元素进行更新一次,可以直接将advance
设置为 1。
2)嵌套任务
嵌套任务则是指外部进度条与内部进度条依次更新,两者有依赖关系,只有内部进度条更新完了,外部进度条才会更新一次:
import time
from rich.progress import Progress
with Progress() as progress:
task1 = progress.add_task("[red]Master Progress Bar...", total=2)
for i in range(2):
task2 = progress.add_task(f"[green]Sub Progress Bar: {i + 1}...", total=10)
for j in range(10):
progress.update(task2, advance=1)
time.sleep(1)
time.sleep(1)
progress.update(task1, advance=1)
2.2 更新任务
当调用add_task
函数时,我们将得到一个 Task ID。当完成某些工作或信息发生更改时,将 Task ID 传给update
函数。通常,在每次完成一个步骤之后需要进行更新completed
值,我们可以通过直接更新completed
参数值或者设置advance
值(advance
的值将会添加道当前completed
值上)来完成。
所以上面我们的示例,也可以采用更新completed
值来实现对应功能:
import time
from rich.progress import Progress
with Progress() as progress:
task = progress.add_task("[red]Master Progress Bar...", total=7)
for i in range(7):
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
:所需要渲染的其他字段
import time
from rich.progress import Progress
with Progress() as progress:
p, total = 0, 10
description = f"[red]Loading ({p}/{total})..."
task = progress.add_task(description.format(), total=total)
for p in range(1, total + 1):
description = f"[red]Loading ({p}/{total})..."
progress.update(task, completed=p, description=description)
time.sleep(1)
2.3 隐藏任务
我们可以通过更新任务的visible
值来显示/隐藏任务,默认情况下,任务是可见的,但我们也可以通过调用add_task
函数并设置visible=False
来添加不可见任务。
2.4 瞬态进度
通常,当我们退出进度条上下文管理器(或者通过调用stop
方法)时,最后刷新的显示仍保留在终端中,光标位于下一行。如果我们希望进度条在退出时消失,从而使得终端中的输出更简洁,可以在构造Progress
构造函数中设置transient=True
。
with Progress(transient=True) as progress:
task = progress.add_task("Working", total=100)
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
自定义进度条,最终展示效果如下图所示
import time
from rich.progress import Progress, BarColumn, SpinnerColumn, TimeRemainingColumn, TimeElapsedColumn
with Progress(
"[progress.description]{task.description}({task.completed}/{task.total})",
SpinnerColumn(finished_text="[green]✔"),
BarColumn(),
"[progress.percentage]{task.percentage:>3.2f}%",
"[yellow]⏱",
TimeElapsedColumn(),
"[cyan]⏳",
TimeRemainingColumn()) as progress:
description = "[red]Loading"
task = progress.add_task(description, total=1000)
for p in range(1, 1001):
if p != 1000:
description = "[red]Loading"
else:
description = "[green]Finished"
progress.update(task, completed=p, description=description)
time.sleep(0.02)
如果 Rich 所提供的Column无法满足你的需求,你也可以通过继承ProgressColumn
类进行扩展,并像上述方式一样进行使用。
2.9 表格列
Rich 为Progress
实例任务构建了一个Table
对象,我们可以通过在Column
构造器中指定table_column
参数来自定义任务表列的创建方式,该参数的值应该是Column
实例。
以下示例演示了一个进度条,其中描述占终端的宽度的 1/3(也就是Column(ratio=1)
),进度条则占了 2/3,
import time
from rich.table import Column
from rich.progress import Progress, BarColumn, TextColumn
text_column = TextColumn("{task.description}", table_column=Column(ratio=1))
bar_column = BarColumn(bar_width=None, table_column=Column(ratio=2))
progress = Progress(text_column, bar_column, expand=True)
with progress:
for n in progress.track(range(100)):
progress.print(n)
time.sleep(0.1)
2.10 打印/记录日志
Progress
类将创建一个内部控制台对象,我们可以用过progress.console
访问该对象。如果希望打印或登录到此控制台,输出将显示在进度条上方。下面示例中可以直接用progress.print
代替progress.console.print
。
import time
from rich.progress import Progress
with Progress() as progress:
task = progress.add_task("twiddling thumbs", total=10)
for job in range(10):
progress.console.print(f"Working on job #{job}")
time.sleep(1)
progress.advance(task)
同样,我们也可以将另外的Console
对象传递给Progress
类进行使用:
from my_project import my_console
with Progress(console=my_console) as progress:
my_console.print("[bold blue]Starting work!")
do_work(progress)
2.11 重定向标准输出/标准错误
为了避免破环进度条显示的视觉效果,Rich 将重定向stdout
和stderr
,以便我们可以使用内置的print
语句。此功能默认是开启的,但可以通过将redirect_stdout
或redirect_stderr
设置为False
。
2.12 自定义
如果Progress
类在进度条显示方面没有提供我们所需要的内容,还可以通过覆写get_renderables
方法来达到想要的效果。例如,以下类将在进度条显示周围呈现一个面板:
import time
from rich.panel import Panel
from rich.progress import Progress
class FrameProgress(Progress):
def get_renderables(self):
yield Panel(self.make_tasks_table(self.tasks), expand=False)
with FrameProgress() as progress:
task1 = progress.add_task("[red]Master Progress Bar...", total=10)
for i in range(5):
task2 = progress.add_task(f"[green]Sub Progress Bar: {i + 1}...", total=10)
for j in range(10):
progress.update(task2, advance=1)
time.sleep(1)
time.sleep(1)
progress.update(task1, completed=1)
3. 多重进度
对于单个Progress
实例,每个任务不能有不同的列。但是,你可以在实时显示中拥有任意数量的进度实例。
4. 示例
Rich 官方提供了一个示例脚本来演示进度条显示的实际应用,该脚本可以下载多个带有进度条、传输速度和文件大小的并发文件。