Python技术迷

Python制作进度条,原来有这么多方法!

我先不整那些虚的哈,直接说个场景你肯定熟。

就那种:你写了个脚本,处理十几万个文件、搞数据清洗、跑模型训练,命令行敲下去之后,屏幕一闪……就没动静了。风扇呼呼转,你也不知道它是卡死了,还是在老老实实干活。 昨天晚上我在公司楼下奶茶店等脚本跑,盯着黑乎乎的终端,真有点想给它安个“心电图监护仪”,于是就顺手把几种 Python 进度条的写法捋了一遍。

结果一看,哎哟,方法还真不少。

先来点儿最朴素的:print 土味进度条

最原始的办法其实谁都会,就是不停地 print。 但稍微讲究一点,我们可以用 \r 回车符,把光标拉回一行开头,覆盖原来的内容,看起来就像一条在动的进度条。

import time
import sys

for i in range(101):
# \r 回到行首,end="" 不换行
    sys.stdout.write(f"\r进度:{i:3d}%")
    sys.stdout.flush()
    time.sleep(0.05)

print("\n任务完成!")

这段跑一下,你就会看到命令行上只占一行,数字从 0% 慢慢涨到 100%。 几个小点顺手说一下:

  • \r 是“回车但不换行”,光标回到行首
  • sys.stdout.flush() 是强制把缓冲区的内容推到终端,不然有可能几次才刷新一次
  • f"{i:3d}%" 是把数字格式化成宽度 3 的整数,比如   1、 10、100

这个东西已经能解决 80% 的焦虑:至少知道程序还在跑。

再高级一点:自己封一个小进度条函数

如果你想来点“条条”,比如 [####------] 40% 这种,可以自己包一个小函数,写一次处处用。

import sys
import time

defprogress_bar(curr, total, bar_len=30):
    percent = curr / total
    filled = int(bar_len * percent)
    bar = "#" * filled + "-" * (bar_len - filled)
    sys.stdout.write(f"\r[{bar}] {percent*100:6.2f}% ({curr}/{total})")
    sys.stdout.flush()

if __name__ == "__main__":
    total = 200
for i in range(1, total + 1):
# 假装在干活
        time.sleep(0.02)
        progress_bar(i, total)
    print("\n全部搞定!")

你可以想象一下日常脚本里这么用:

  • 扫描一个目录下所有文件,每处理完一个文件就 progress_bar(done, total)
  • 跑数据库迁移,一条一条插,顺便看看还要等多久
  • 训练模型的时候,外面再套一层 epoch 级别的进度,每个 epoch 里再来个 batch 级别的(当然这个就有点花了)

这个自定义函数的好处就是轻量、不用装库,啥环境都能跑。

懒人福音:tqdm,直接一行搞定

说实话,自己写进度条写两次你就会嫌烦…… Python 圈子最出名的进度条库就是 tqdm,名字看着怪,其实就是 “进度条” 那意思。用起来非常不费脑子。

先装一下:

pip install tqdm

最常见的用法,直接包在 for 外面:

from tqdm import tqdm
import time

for i in tqdm(range(1000), desc="处理数据"):
# 模拟耗时操作
    time.sleep(0.001)

跑起来你会看到一条很专业的进度条,里面自带:

  • 当前进度百分比
  • 已用时间、预计剩余时间
  • 每秒迭代次数

基本上你能想到的信息它都给你弄好了。

如果你不是简单的 range,而是一个列表、生成器啥的,也可以直接包:

from tqdm import tqdm

items = ["a.txt", "b.txt", "c.txt"]

for name in tqdm(items, desc="读取文件"):
# 在这里处理 name
pass

还有一种场景是总量你知道,但循环不是按“列表”写的,比如你在 while 里下载文件、或者不停从队列里取任务,这时候可以用“手动更新”的写法:

from tqdm import tqdm
import time

total = 100
with tqdm(total=total, desc="下载中") as bar:
for i in range(total):
# 假装每次下载一块
        time.sleep(0.03)
        bar.update(1)  # 手动 +1

这个 with 写法有个好处:进度条结束后它会帮你把光标换到下一行,不会把后面的日志写到同一行上。

想要更好看一点:rich 的花式进度条

有时候你跑的不只是一个循环,比如同时有“下载文件”“解析数据”“写入数据库”三件事,你就会开始嫌普通进度条太简陋。 这时候可以考虑下 rich 这个库,既能画颜色、表格、提示框,也有挺强的进度条。

先装:

pip install rich

如果你只是想来个和 tqdm 差不多的体验,可以直接用它内置的 track:

from time import sleep
from rich.progress import track

for i in track(range(100), description="Rich 进度条在跑..."):
    sleep(0.03)

稍微复杂一点,可以玩多任务进度条:

from time import sleep
from rich.progress import Progress

with Progress() as progress:
    task_download = progress.add_task("下载文件", total=100)
    task_parse = progress.add_task("解析数据", total=200)

whilenot progress.finished:
ifnot progress.finished:
            progress.update(task_download, advance=1)
            progress.update(task_parse, advance=2)
            sleep(0.05)

你会看到终端里两条进度条一起跑,看起来就很有那种“小型监控面板”的感觉。rich 的优势主要是:

  • 多任务、多行进度条
  • 自带颜色、样式,很适合做一点“可视化”
  • 和它自己的日志、表格、Traceback 能组合在一起用

如果你是在给团队做一个 CLI 工具,想让别人一看就觉得“哎呦,这玩意还挺专业”,rich 很值得上。

说一点我们经常会碰到的:下载文件。 如果你用 requests 去下一个大文件,没进度条的时候就只能干等,体验很迷。

换成 tqdm 加一点小逻辑,就能看着它慢慢涨:

import requests
from tqdm import tqdm

defdownload_file(url, filename):
with requests.get(url, stream=True) as r:
        r.raise_for_status()
        total = int(r.headers.get("content-length", 0))
        chunk_size = 1024

with open(filename, "wb") as f, tqdm(
            total=total,
            unit="B",
            unit_scale=True,
            unit_divisor=1024,
            desc=f"下载 {filename}"
        ) as bar:
for chunk in r.iter_content(chunk_size=chunk_size):
ifnot chunk:
continue
                f.write(chunk)
                bar.update(len(chunk))

if __name__ == "__main__":
    download_file("https://example.com/big-file.zip", "big-file.zip")

这里几个点顺手说说:

  • stream=True 是流式下载,不一次性把文件全读到内存
  • content-length 是服务端告诉你的总字节数,有这个才能画“准一点”的进度条
  • bar.update(len(chunk)) 就是根据真实下载字节数更新进度

这个套路改一改其实也能用在:

  • 大批量导出导入数据(按行更新进度)
  • 读一个超大的 CSV,每处理一行就 update(1)
  • Kafka / MQ 消费消息时,做一个简单的“今日消费多少条”的监控

这个是真实踩过的坑哈: 你在一边用 tqdm 画进度条,一边 print 或 logging.info 打日志,结果屏幕上花里胡哨一大堆,进度条还被打断成好几段,看着就头大。

几个小建议:

  1. 日志尽量打文件里,进度条留给终端logging 配个 FileHandler,终端就让进度条独占。

  2. 如果是批量脚本,不太需要保留进度条,可以让它结束后“擦掉”:

    for x in tqdm(range(100), desc="处理中", leave=False):
        ...

    leave=False 的意思就是结束后别留下那条进度条。

  3. 在 CI / 日志系统里跑脚本,没必要画进度条 这种环境下你可以加个参数控制,比如:

    defiter_with_progress(it, use_tqdm=True):
    if use_tqdm:
    from tqdm import tqdm
    return tqdm(it)
    return it

随便碎碎念下我的个人习惯,你可以对照自己的场景选一个:

  • 写个一次性的小脚本,本地跑跑: 随手用 \r + print 或者自己那个 progress_bar 函数就够了

  • 日常长期用的 CLI 工具、数据处理脚本: 直接上 tqdm,几乎零成本,还自带 ETA、吞吐量

  • 想做得好看一点、任务又比较多: 用 rich 的 Progress,多条进度条一起跑,效果不错

  • 和网络、IO 强相关的长任务(下载/上传/同步): 推荐 tqdm 的手动更新写法,按真实处理的字节/记录数更新,更靠谱

反正一句话:别再让用户(包括未来的你自己)在黑漆漆的终端前干等了,随便选一个进度条加上去,成本低,体验提升特别明显。

我这边今天就先到这儿,等下还得改一个线上脚本的日志输出,不然进度条又要被打断了…

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB。