Python中tqdm进度条功能深度解析

全文一览

  • 一、前言
  • 1.1 需求来源
  • 1.2 tqdm 快速安装
  • 二、代码解析
  • 2.1 原理简介
  • 2.2 快速上手
  • 2.2.1 trange
  • 2.2.2 其他 Iterable
  • 2.3 定制化显示
  • 2.3.1 自定义前后缀
  • 2.3.2 自定义刻度
  • 三、实战演示
  • 四、运行效率
  • 4.1 简单加法
  • 4.2 循环体耗时运算
  • 4.3 结论
  • 五、完整代码
  • 一、前言

      很多时候,当我们的循环时间较长时,往往想要实时了解当前的进度,以期预知剩余的时间,这让我们对代码的输出有了更强的反馈感。那么,要如何实现呢?

    1.1 需求来源

      在循环体里添加 print 语句或许能够帮上忙,但是这样又会遮蔽有用的信息。很自然地,我们会想到:

    能不能设计一个类似下载安装进度条那样的东西,来简洁直观地展示进度呢?

      这也正是 tqdm 要实现的目标!

    1.2 tqdm 快速安装

      由于 tqdm 不是 Python 的内置库,在使用前需要先进行安装。我们可以在终端输入以下命令进行安装:

    # 直接安装,略慢
    # pip install tqdm
    # 使用国内(清华)镜像安装,推荐
    pip install tqdm -i https://pypi.tuna.tsinghua.edu.cn/simple
    

    二、代码解析

    2.1 原理简介

      在 tqdm 库中,可以使用 tqdm 函数对可迭代类型(Iterable)数据进行装饰,返回 tqdm.std.tqdm 类型的可迭代数据(Iterable)。
    原理解析
      可见,装饰前后,二者都是可迭代的,区别在于后者会自动显示进度条!

    进度条
      注意:tqdm.std.tqdm 类型只能遍历一次,不能重复使用!

    2.2 快速上手

    2.2.1 trange

      trange 函数是 tqdm 中最常用的函数,从其源码中可以看出,trange 是 tqdm 对 range 类型数据的装饰:

    def trange(*args, **kwargs):
        """Shortcut for tqdm(range(*args), **kwargs)."""
        return tqdm(range(*args), **kwargs)
    

      使用 trange 可以 替代 range 进行迭代,并显示进度条:

    import tqdm
    import time
    
    for i in tqdm.trange(20):
        pass
        time.sleep(0.5)
    

    2.2.2 其他 Iterable

      除了对 range 类型,tqdm 还能装饰所有常见的可迭代类型:str、tuple、list、set、dict ……

    import tqdm
    
    a = "abcdefg"
    b = (1, 2, 3, 4, 5)
    c = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
    d = {'apple', 'banana', 'cherry', 'date'}
    e = {'a': "Apple", 'b': "Banana", 'c': "Cherry", 'd': "Date"}
    
    for i in tqdm.tqdm(a): pass
    for i in tqdm.tqdm(b): pass
    for i in tqdm.tqdm(c): pass
    for i in tqdm.tqdm(d): pass
    for i in tqdm.tqdm(e): pass
    

      这些 Iterable 类型都能用于创建进度条:
    其他类型

    2.3 定制化显示

      在使用设置函数前,应特意将 tqdm 对象命名为新的变量(如 “progress_bar ”),以便调用函数使用。

    2.3.1 自定义前后缀

      虽然在 2.2 中,进度条打印的信息已经非常详细了,但或许这还不能满足我们定制化的需要!

    当安装一个含多个组件的软件时,如何显示当前安装到哪个组件了呢?

      tqdm 为此编写了 set_description、set_description_str、set_postfix、set_postfix_str 等常用函数,在此仅以 set_description 和 set_postfix 为例进行演示。

    import time
    import random
    from tqdm import tqdm
    
    # 组件中文名称
    install_components_cn = ["运行时库和依赖项", "驱动程序", "数据库", "命令行工具", "网络组件", "第三方库和框架"]
    
    progress_bar = tqdm(install_components_cn)
    count = 0
    for info in progress_bar:
        count += 1
        # 前缀
        progress_bar.set_description(f"正在安装{info}")
        # 后缀
        progress_bar.set_postfix({"info": f"第{count}项"})
        # 模拟安装时间,设置随机 0.5~3.0 秒
        time.sleep(random.uniform(0.5, 3.0))
    

      set_description 以字符串为参数,为进度条设置前缀;set_postfix 以字典为参数,为进度条添加备注信息。
    前后缀

    set_description_str 方法跟 set_description 方法目的、用法一致;
    set_postfix_str 方法跟 set_postfix 方法目标一致,但前者传入字符串,后者传入字典。

    2.3.2 自定义刻度

      在实际处理工作流 stream 时,每个文件 file 的大小并不完全一样,处理后进度条的进展不能视作均等的 1/n 。

    那么,如何设置进度条前进的刻度跟文件大小成正相关呢?

      这时候,不能直接使用 tqdm 函数对工作流进行包装,而应该使用 total 关键字设置总刻度的长度,再用 update 方法让进度条前进一定的刻度。
      注意:此时 total 关键字的参数必须进行设置,因为其默认为 None!否则虽然会显示进度,但不会有进度条!

    import time
    import random
    from tqdm import tqdm
    
    # 模拟多个文件,每个文件分别有 4、9、3、7 段
    files = [[1, 2, 3, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3], [1, 2, 3, 4, 5, 6, 7]]
    
    # 设置进度条总长度 total 为 files 的总段数
    progress_bar = tqdm(total=sum(len(file) for file in files))
    count = 0
    for file in files:
        count += 1
        # 设置前后缀
        # progress_bar.set_description(f"file:{file}")
        # progress_bar.set_postfix({"info": f"第{count}项"})
        time.sleep(random.uniform(0.5, 3.0))
        # 进度条向前滚动 len(file) 刻度
        progress_bar.update(len(file))
    progress_bar.close()
    

      “progress_bar.close()” 强烈推荐保留,其作用在于确保进度条能够达到 100% 并完成显示!
    update
      在更新进度条的时候,我们同样还可以设置前后缀!

    将 tqdm 的 total 关键字设置为 1,然后将 update 的参数设置为 len(file) / sum(len(file) for file in files),仍能达到相同的效果。

    三、实战演示

      以安装 my_app 的软件为例,其基本信息如下 my_app 字典所示:

    import time
    import random
    from tqdm import tqdm
    
    my_app = {"运行时库和依赖项": 7, "驱动程序": 19, "数据库": 4,
              "命令行工具": 3, "网络组件": 4, "第三方库和框架": 23}
    
    
    def set_up(app):
        total_size = sum(v for k, v in app.items())  # 总大小为 100
        progress_bar = tqdm(app, total=total_size)
        for component in progress_bar:
            size = app[component]
            progress_bar.set_description(f"正在安装{component}")
            # progress_bar.set_description_str(f"正在安装{component}")
            progress_bar.set_postfix({"size": f"{size}MB"})
            # progress_bar.set_postfix_str(f"size:{size}MB")
            time.sleep(random.uniform(0.5, 3.0))  # 模拟安装用时
            progress_bar.update(size)
        progress_bar.close()
    
    
    if __name__ == '__main__':
        set_up(my_app)
    

      运行结果如下图所示:
    进度条展示

    四、运行效率

    引入这样一个实时显示进度的功能,会对我们的代码执行效率有多大影响呢?

      对此,我们设计简单的加法运算和耗时的处理两种场景,代入不同大小的 n,分析使用 tqdm 与否的执行时间差。

    4.1 简单加法

      编写是否使用 tqdm 进行求和的两个函数,比较它们在 n = 100、100 000、100 000 000 时各自的运行时间:
      

    import time
    from tqdm import trange
    
    
    def run_without_tqdm(n):
        start_time = time.time()
        res = 0
        for i in range(n):
            res += i
        print(time.time() - start_time)
    
    
    def run_with_tqdm(n):
        start_time = time.time()
        res = 0
        for i in trange(n):
            res += i
        print(time.time() - start_time)
    
    
    if __name__ == '__main__':
        # n = 10 ** 2
        n = 10 ** 5
        # n = 10 ** 8
        run_without_tqdm(n)
        run_with_tqdm(n)
    

      如下图所示,带 tqdm 的运行时间是原函数的无数倍、13倍、4倍:
    对比1

    4.2 循环体耗时运算

      编写是否使用 tqdm 进行等待的两个函数,比较它们在 n = 100、1 000、10 000 时各自的运行时间:
      

    import time
    from tqdm import trange
    
    
    def run_without_tqdm(n):
        start_time = time.time()
        for i in range(n):
            time.sleep(0.001)
        print(time.time() - start_time)
    
    
    def run_with_tqdm(n):
        start_time = time.time()
        for i in trange(n):
            time.sleep(0.001)
        print(time.time() - start_time)
    
    
    if __name__ == '__main__':
        # n = 10 ** 2
        # n = 10 ** 3
        n = 10 ** 4
        run_without_tqdm(n)
        run_with_tqdm(n)
    

      如下图,带 tqdm 的运行时间是原函数的2倍、1倍、1倍:
    对比2

    4.3 结论

      从 1.1 和 4.2 的对比结果中,不难看出,使用 tqdm 会带来一定的额外消耗。但当循环足够多或者循环体耗时较长时,tqdm 对效率的影响可以忽略不计!

    五、完整代码

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # @FileName  : progress_bar.py
    # @Time      : 2024/1/22 14:31
    # @Author    : Carl.Zhang
    # Function   : progress_bar
    
    # 2.2 快速上手 —— trange
    import tqdm
    import time
    
    for i in tqdm.trange(20):
        pass
        time.sleep(0.5)
    
    # # 2.2 快速上手 —— 其他 Iterable:str、tuple、list、set、dict
    # import tqdm
    #
    # a = "abcdefg"
    # b = (1, 2, 3, 4, 5)
    # c = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
    # d = {'apple', 'banana', 'cherry', 'date'}
    # e = {'a': "Apple", 'b': "Banana", 'c': "Cherry", 'd': "Date"}
    #
    # for i in tqdm.tqdm(a): pass
    # for i in tqdm.tqdm(b): pass
    # for i in tqdm.tqdm(c): pass
    # for i in tqdm.tqdm(d): pass
    # for i in tqdm.tqdm(e): pass
    
    
    # # 2.3 定制化显示 —— 前缀、后缀
    # import time
    # import random
    # from tqdm import tqdm
    #
    # # 组件中英文名称
    # # install_components_en = ["Runtime libraries and dependencies", "Drivers", "Database", "Command-line tools",
    # #                          "Networking components", "Third-party libraries and frameworks"]
    # install_components_cn = ["运行时库和依赖项", "驱动程序", "数据库", "命令行工具", "网络组件", "第三方库和框架"]
    #
    # progress_bar = tqdm(install_components_cn)
    # count = 0
    # for component in progress_bar:
    #     count += 1
    #     # 前缀
    #     # progress_bar.set_description(f"正在安装{component}")
    #     # 后缀
    #     progress_bar.set_postfix({"info": f"第{count}项"})
    #     # 模拟安装时间,设置随机 0.5~3.0 秒
    #     time.sleep(random.uniform(0.5, 3.0))
    
    
    # # # 2.3 定制化 —— stream流 与 update更新
    # import time
    # import random
    # from tqdm import tqdm
    #
    # # 模拟多个文件,每个文件分别有 4、9、3、7 段
    # files = [[1, 2, 3, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3], [1, 2, 3, 4, 5, 6, 7]]
    #
    # # 设置进度条总长度 total 为 files 的总段数
    # progress_bar = tqdm(total=sum(len(file) for file in files))
    # count = 0
    # for file in files:
    #     count += 1
    #     # 设置前后缀
    #     progress_bar.set_description(f"file:{file}")
    #     progress_bar.set_postfix({"info": f"第{count}项"})
    #     time.sleep(random.uniform(0.5, 3.0))
    #     # 进度条向前滚动 len(file)/total 刻度
    #     progress_bar.update(len(file))
    # progress_bar.close()
    
    
    # # 3 实战演示
    # import time
    # import random
    # from tqdm import tqdm
    #
    # my_app = {"运行时库和依赖项": 7, "驱动程序": 19, "数据库": 4,
    #           "命令行工具": 3, "网络组件": 4, "第三方库和框架": 22}
    #
    #
    # def set_up(app):
    #     total_size = sum(v for k, v in app.items())  # 总大小为 100
    #     progress_bar = tqdm(app, total=total_size)
    #     for component, size in app.items():
    #         progress_bar.set_description(f"正在安装{component}")
    #         # progress_bar.set_description_str(f"正在安装{component}")
    #         # progress_bar.set_postfix({"size": f"{size}MB"})
    #         progress_bar.set_postfix_str(f"size:{size}MB")
    #         time.sleep(random.uniform(0.5, 3.0))  # 模拟安装用时
    #         progress_bar.update(size)
    #     progress_bar.close()
    #
    #
    # if __name__ == '__main__':
    #     set_up(my_app)
    
    
    # # 4.1 运行效率 —— 简单加法
    # import time
    # from tqdm import trange
    #
    #
    # def run_without_tqdm(n):
    #     start_time = time.time()
    #     res = 0
    #     for i in range(n):
    #         res += i
    #     print(time.time() - start_time)
    #
    #
    # def run_with_tqdm(n):
    #     start_time = time.time()
    #     res = 0
    #     for i in trange(n):
    #         res += i
    #     print(time.time() - start_time)
    #
    #
    # if __name__ == '__main__':
    #     # n = 10 ** 2
    #     n = 10 ** 5
    #     # n = 10 ** 8
    #     run_without_tqdm(n)
    #     run_with_tqdm(n)
    
    
    # # 4.2 运行效率 —— 循环体耗时运算
    # import time
    # from tqdm import trange
    #
    #
    # def run_without_tqdm(n):
    #     start_time = time.time()
    #     for i in range(n):
    #         time.sleep(0.001)
    #     print(time.time() - start_time)
    #
    #
    # def run_with_tqdm(n):
    #     start_time = time.time()
    #     for i in trange(n):
    #         time.sleep(0.001)
    #     print(time.time() - start_time)
    #
    #
    # if __name__ == '__main__':
    #     # n = 10 ** 2
    #     # n = 10 ** 3
    #     n = 10 ** 4
    #     run_without_tqdm(n)
    #     run_with_tqdm(n)
    
    
    # # 封面
    # import time
    # from tqdm import trange
    #
    # download = trange(15)
    # for i in download:
    #     download.set_description("下载中...")
    #
    #
    # install = trange(20)
    # for i in install:
    #     install.set_description("安装中...")
    #
    #
    # update = trange(35)
    # for i in update:
    #     update.set_description("升级中...")
    #     time.sleep(0.5)
    

    更多 python 的使用方法和应用,敬请关注后续更新~

    作者:今夕晚风依旧

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python中tqdm进度条功能深度解析

    发表回复