
本文详解为何自定义 print(..., end='\r') 进度条在 df.apply() 中会重复出现多行,以及如何通过 tqdm 实现稳定、高效、jupyter 友好的单行动态进度指示。
本文详解为何自定义 print(..., end='\r') 进度条在 df.apply() 中会重复出现多行,以及如何通过 tqdm 实现稳定、高效、jupyter 友好的单行动态进度指示。
在使用 pandas.DataFrame.apply() 处理大规模数据(如 5000+ 行)时,若手动实现基于 print(..., end='\r') 的进度条(如题中 progressBar() 函数),极易出现多条进度条叠加显示(见截图),而非预期的单条实时更新。根本原因在于:Jupyter 的输出缓冲机制与 apply() 的内部执行逻辑不兼容——apply 在底层可能分块调用、触发多次内核刷新,而 \r 仅作用于当前输出行;当新 print 调用发生在不同内核消息周期时,旧行未被覆盖,新行便另起一行渲染,导致“进度条堆积”。
手动调用 sys.stdout.flush() 不仅无法可靠解决该问题(因 Jupyter 不完全遵循标准 stdout 行为),还会显著拖慢执行速度,违背进度条本应轻量、无侵入的设计初衷。
✅ 正确解法:使用专为交互式环境优化的 tqdm 库。它原生支持 Jupyter Notebook/Lab,提供线程安全、自动刷新、动态长度适配的进度条,并深度集成 pandas:
from tqdm.notebook import tqdm
import pandas as pd
# 启用 pandas 扩展(只需一次)
tqdm.pandas(desc="Calculating")
# 定义处理函数(无需维护状态或手动打印)
def myf(row):
row['1'] = 100
return row # 注意:apply 需要返回值以构建结果
# 替换 df.apply() 为 progress_apply()
df = pd.DataFrame(index=range(0, 5000), columns=['1','2','3','4','5'])
df = df.progress_apply(myf, axis=1) # ✅ 单条、平滑、实时更新的进度条⚠️ 关键注意事项:
- 必须使用 from tqdm.notebook import tqdm(非 tqdm.tqdm),否则在 Jupyter 中可能退化为文本模式或报错;
- tqdm.pandas() 是一次性注册步骤,之后所有 progress_apply() 调用均自动启用进度条;
- 若函数需修改原 DataFrame 而非返回新行(如 inplace=True 场景),建议改用 df.loc[:, '1'] = 100 等向量化操作——既免去进度条需求,性能也提升数十倍;
- progress_apply() 返回新 DataFrame,如需原地修改,请显式赋值(如 df = df.progress_apply(...))。
总结:避免在 Jupyter 中自行实现 \r 进度条;拥抱 tqdm.notebook + progress_apply() 组合,可获得开箱即用、稳定可靠、专业级的可视化进度反馈。


















