Gradio 自定义进度条实战:gr.Progress 与 tqdm 全攻略
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
本指南基于 Gradio 仓库官方进阶文档《Progress Bars》(guides/04_additional-features/05_progress-bars.md)编写,围绕"如何给耗时函数显示实时进度"这一主题展开。文中将以 demo/progress_simple/run.py 等真实可运行示例为主线,并结合 gradio/helpers.py 中gr.Progress的实现源码,系统讲解手动更新进度、基于可迭代对象的进度追踪,以及自动接管tqdm.tqdm进度条的三种用法。读完本文,你将能在自己的gr.Interface或gr.Blocks应用中精确控制并向用户展示每一个处理阶段的进展。
核心机制:为函数注入gr.Progress参数
Gradio 之所以能显示自定义进度条,靠的是一种非常轻量的"注入"约定:只要在你的函数里新增一个以gr.Progress()实例为默认值的参数,Gradio 就会自动识别它,并把当前事件对应的进度通道绑定到该实例上。你不需要显式创建任何客户端代码或前端组件——绑定、消息推送与界面渲染全部由框架完成。
注意参数位置的约定:
Progress参数应紧跟在常规输入参数之后。从源码看,gradio/helpers.py 中的special_args()通过isinstance(param.default, Progress)判断特殊参数(见L979),并把传入的gr.Progress()实例自动插入输入列表对应下标;因此在函数签名中放错位置或写成关键字参数都可能不被识别。
这个默认值参数被识别后,接下来你有两条更新进度的途径,它们可以混合使用:
- 直接调用
progress实例,传入一个0 ~ 1之间的小数表示完成比例; - 使用
progress.tqdm(...)方法包装任意可迭代对象,让进度随迭代自动推进。
Progress类的官方 docstring(见 gradio/helpers.pyL673-L689)给出了最小用法示例,下文将逐一展开。
快速上手:一个最简单的自定义进度条
仓库中与本文档配套的最小可运行示例位于 demo/progress_simple/run.py,完整代码只有 17 行:
import gradio as gr import time def slowly_reverse(word, progress=gr.Progress()): progress(0, desc="Starting") time.sleep(1) progress(0.05) new_string = "" for letter in progress.tqdm(word, desc="Reversing"): time.sleep(0.25) new_string = letter + new_string # type: ignore return new_string demo = gr.Interface(slowly_reverse, gr.Text(), gr.Text(), api_name="predict") if __name__ == "__main__": demo.launch()运行该文件后,在输入框中输入一个单词并提交,你会依次看到:
- 阶段一:进度条停驻在
0(0%)并显示描述文本"Starting"; - 阶段二:进度条跳到
0.05(5%); - 阶段三:进度条随
word中每个字符的逐个处理平滑推进,描述变为"Reversing"。
这个例子同时示范了两种更新方式,下面分别深入讲解其参数细节与底层行为。
方式一:手动更新进度
Progress.__call__(见 gradio/helpers.pyL736-L764)支持三种形态的入参,签名如下:
progress( progress: float | tuple[int, int | None] | None, desc: str | None = None, total: int | float | None = None, unit: str = "steps", )各参数的语义(与源码 docstring 一致):
| 参数 | 类型 | 含义 |
|---|---|---|
progress | float | 表示整体完成比例的小数,取值应落在0到1之间,如progress(0.5)表示 50%; |
progress | tuple[int, int \| None] | 传入元组时,第一个数代表"已完成步数"index,第二个数代表"总步数"total(未知则传None),由index / total换算成比例; |
progress | None | 隐藏进度条。当阶段划分不明确、或函数即将进入无法量化的环节时,可调用progress(None)让进度条消失; |
desc | str | 进度条上显示的描述文字,例如"Cleaning Images"; |
total | int \| float | 预估总步数(使用元组形式时同样可传); |
unit | str | 迭代单位名称,默认是"steps",用于展示如3/10 steps。 |
一个典型的多阶段手动更新示例(取自 demo/progress/run.py 的clean_imgs函数):
def clean_imgs(text, progress=gr.Progress()): progress(0.2, desc="Collecting Images") time.sleep(1) progress(0.5, desc="Cleaning Images") time.sleep(1.5) progress(0.8, desc="Sending Images") time.sleep(1.5) return "done"可以据此把一项长任务拆成若干可命名的工序,每道工序开始前调用一次progress,让用户始终清楚当前处于哪个阶段。
方式二:用progress.tqdm()追踪迭代
Progress.tqdm()(见 gradio/helpers.pyL766-L796)提供与tqdm库类似的迭代式进度追踪。当Progress处于有效绑定状态时,它并不会真的逐项 yield,而是把一个"可追踪迭代器"(TrackedIterable)压入内部栈,并把进度计算委托给底层队列:
for item in progress.tqdm(iterable, desc="Reversing"): ...其可选参数desc、total、unit与手动更新的语义完全一致:
- 当传入的
iterable实现了__len__(如list、range)时,total会取len(iterable),进度精确; - 当迭代对象长度未知时(如生成器),可通过
total显式给出预估总步数,否则进度条按不确定模式展示。
嵌套进度:外层 + 内层双层循环
与tqdm一样,Progress.tqdm天然支持嵌套。例如 demo/progress/run.py 中的load_nested_set演示了"外层列表 + 内层列表"的嵌套追踪:
def load_nested_set(text, text2, progress=gr.Progress()): imgs = [[None] * 8] * 3 for img_set in progress.tqdm(imgs, desc="Nested list"): time.sleep(2) for img in progress.tqdm(img_set, desc="inner list"): time.sleep(0.1) return "done"从前端表现看,内层循环推进时会显示"细分进度",内层结束后外层条继续推进。这一行为由源码中的"迭代器栈"实现:每次调用tqdm()都append一个TrackedIterable,每次迭代耗尽后pop出栈(见 gradio/helpers.pyL784-L793),从而支持任意层级的递归跟踪。
追踪未知长度的生成器
load_random函数展示了传入生成器、长度完全未知的场景:
def load_random(data, progress=gr.Progress()): def yielder(): for i in range(0, random.randint(15, 20)): time.sleep(0.1) yield None for img in progress.tqdm(yielder()): pass return "done"生成器没有__len__且未传total时,进度条会以不确定状态展示,但仍能实时刷新"已完成 X 项"。
方式三:track_tqdm=True自动追踪已有 tqdm
如果你(或你调用的第三方库)的代码里已经写了原生的tqdm.tqdm,并不想改造它,只要把默认参数改成:
def fn(..., progress=gr.Progress(track_tqdm=True)): for i in tqdm.tqdm(range(100)): ...此时函数体内所有tqdm.tqdm的进度都会被自动转发到 Gradio 进度条上,无需任何手工同步。
该特性的背后是patch_tqdm()对tqdm类的一次全局"猴子补丁"(见 gradio/helpers.pyL837-L902):
tqdm未安装时直接return(不会报错);- 对
tqdm.tqdm的__init__、update、close、__exit__、__iter__五个方法做包装,保存原方法为*_orig; - 每个方法在执行真实逻辑前,先从
LocalContext取出当前线程/上下文中的Progress实例,若存在则调用其tqdm()/update()/close()把进度同步出去; create_tracker()(见L905-L915)会再用function_wrapper在函数执行前把Progress实例写入LocalContext.progress,结束后清除,避免跨请求串扰。
仓库用两个按钮演示了它的两种典型场景(见 demo/progress/run.py):
def track_tqdm(data, progress=gr.Progress(track_tqdm=True)): for i in tqdm.tqdm(range(5), desc="outer"): for j in tqdm.tqdm(range(4), desc="inner"): time.sleep(1) return "done"即你自己代码里的嵌套原生 tqdm;而bind_internal_tqdm函数则调用datasets.load_dataset(...),演示了第三方库内部使用的 tqdm同样会被自动接管(该 demo 的 requirements.txt 声明了tqdm与datasets两个依赖)。这是模型下载、数据集加载类场景下非常实用的能力——你一行进度代码都不用写。
进度数据的底层流转链路
把进度从 Python 函数一路送到浏览器,涉及如下调用链,理解它有助于排查"进度不显示"的问题:
- 函数内调用
progress(0.5, desc=...)或迭代progress.tqdm(...); Progress._progress_callback()(见 gradio/helpers.pyL828-L834)从LocalContext中读取当前blocks与event_id,拿到blocks._queue.set_progress的绑定方法;若当前并不处于 Gradio 事件上下文(如纯本地脚本直接调用函数),回调为None,此时__call__直接原样返回传入值、tqdm()退化为普通迭代器——这就是为什么带gr.Progress参数的处理函数也能在单元测试或本地脚本中被安全调用;- 队列系统在
set_progress(见 gradio/queueing.pyL603-L626)中把每个TrackedIterable转成结构化的ProgressUnit(含index、length、unit、progress、desc字段,定义见 gradio/server_messages.pyL12),组装为ProgressMessage(L20)并通过 SSE 推送给前端; - 前端消息循环收到
ProgressMessage后渲染进度条。值得注意:多条消息推送之间做了合并去抖,避免高频更新淹没网络通道。
Blocks 场景下的完整进阶示例
除gr.Interface外,gr.Blocks中同样可以使用进度条。仓库中的 demo/progress/run.py 是更完整的综合演示,覆盖了上文提到的全部用法,其核心布局包括:
Load Set:对定长list使用progress.tqdm(imgs, desc="Loading from list");Load Nested Set:嵌套双层progress.tqdm;Load Random:追踪长度未知的生成器;Clean Images:多阶段手动progress(...)调用;Bind TQDM/Bind Internal TQDM:gr.Progress(track_tqdm=True)接管自身与库内部的原生tqdm;Do All:在多个函数之间共享同一个Progress实例并串联执行,其中使用progress(None)在阶段间隙隐藏进度条(见L72-L78):
def do_all(data, progress=gr.Progress()): load_set(data[text], data[textb], progress) load_random(data, progress) clean_imgs(data[text], progress) progress(None) # 隐藏进度条 wait(text) # 进入无法量化、无需展示进度的环节 return "done"共享同一实例的方式表明:只要把Progress作为参数向下传递,多步骤流水线就能维持一条连续、统一的进度展示。值得注意的是这些函数还演示了把dict({text, textb})直接传给按钮的click作为输入集合,属于 Blocks 中多输入组件的常规写法。
常见问题与注意事项小结
- 进度条不出现:确认参数为位置参数、紧跟普通输入之后,且默认值确为
gr.Progress()实例;special_args()依赖默认值类型识别该参数。 - 函数内已有 tqdm 但没生效:确认函数签名使用的是
gr.Progress(track_tqdm=True)而非默认实例;同时确认环境已安装tqdm(patch_tqdm在缺包时会静默跳过)。 - 调用
progress报IndexError: Index not set.:通常在手动调用update()而内部没有已注册迭代器时发生;请优先使用__call__或tqdm()这两种公开方式。 - 进度停留在 0%:为长度未知的生成器补充
total=...参数,可让前端按比例渲染。 - 隐藏而非清零:若某个阶段不想展示进度条,用
progress(None),而不是传0。
上述机制对应的官方源码入口集中在 gradio/helpers.py(Progress类、patch_tqdm、create_tracker)与 gradio/queueing.py(set_progress),如需深入调试,可沿这两处继续阅读。
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考