跳至内容
Python

Tqdm Python:实用示例指南

tqdm 是一个 Python 库,为循环和可迭代对象提供快速、可扩展的进度条,帮助您直观地查看代码的执行进度。
Tom Farnschläder's photo
Tom Farnschläder
已更新 · 11分钟 阅读

使用 AI 探索

ChatGPTClaudePerplexity

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。

您是否遇到过这种情况:一个运行时间很长的 Python 脚本让您一直猜测屏幕后面到底有没有在执行?

对进度的不确定性可能会让您在快要完成时中断运行,或者对一个其实早已被中断的脚本无限等待。

tqdm Python 库通过为脚本提供进度指示器,解决了这个问题。

tqdm 进度条示例

什么是 tqdm?

Tqdm 是一个 Python 库,为循环 和可迭代对象提供快速、可扩展的进度条。它能以简单的方式跟踪耗时任务的推进情况。

该库的名称在阿拉伯语中意为“进步”(taqadum,تقدّم),也是西班牙语中“I love you so much”(te quiero demasiado)的缩写。

Tdqm 通过计数迭代次数、计算已用时间和剩余时间,并在条形填充中可视化整体进度,以跟踪并更新进度条显示。

它使用智能算法预测剩余时间,并跳过不必要的迭代显示以尽量减少开销。使用 tqdm 具有多项优势,包括:

  • 可视化反馈: 进度条使用户能够看到任务已完成的比例,并预估剩余部分所需时间。
  • 广泛适用: tqdm 可在任何平台(Linux、Windows、Mac、FreeBSD、NetBSD、SunOS)、任意控制台或 GUI 中运行。
  • 易于集成: Tqdm 可与 Jupyter Notebook、常用库如Pandas,以及循环等常见 Python 结构无缝结合。
  • 可定制性: 它提供多种选项,可定制进度条的外观和行为,我们稍后会详细介绍。
  • 性能: 虽然类似的包如 ProgressBar 每次迭代约有 800ns 的开销,但 tdqm 的每次迭代 60ns 的开销要快得多。

如何安装 Tqdm 

与大多数 Python 库一样,安装 tqdm 最简单的方法是使用 pip 包管理器。

pip install tqdm

Tqdm:简单示例

要创建进度条,我们用 tqdm() 函数(从 tqdm 模块导入)包装可迭代对象。来看一个简单示例。函数 time.sleep(0.01) 用作占位,代表每次迭代中应运行的代码。

from tqdm import tqdm
    for i in tqdm(range(1000)):
    time.sleep(0.01)
    # …

动图展示示例代码的进度条在 10 秒内完成。

作为 tdqm(range(n)) 的速记,您也可以使用 trange(n)。以下代码将返回与上一个示例完全相同的输出:

from tqdm import tqdm
    for i in trange(1000):
	time.sleep(0.01)
	# …

两个示例都会返回与上图类似的进度条。

仔细观察进度条,看看它提供了哪些信息。我们来分解一下 tqdm 展示的细节:

  • 进度指示:
    • 迭代完成百分比
    • 条形填充
    • 已完成/总迭代数
  • 度量指标:
    • 已用时间
    • 预计剩余时间
    • 性能(每秒迭代次数)

Tqdm 定制:打造专属进度条

Tqdm 提供多种自定义选项来调整进度条的外观和行为。我们先来看最重要的参数,然后再探讨 tqdm 的高级用法。

为进度条添加描述

常见的定制之一是使用 desc 参数添加描述性标签,使进度条更具信息量,也更便于区分不同的可迭代对象。

例如,添加参数 desc=”Processing large range” 会在进度条左侧显示标题“Processing large range”。

for _ in tqdm(range(20000000), desc="Processing large range"):
	continue

建议您在自己的环境中分别带和不带 desc 参数运行上述代码,观察差异。

指定总迭代次数

total 参数用于指定循环的总迭代次数,从而让 tqdm 更准确地估算剩余时间与完成百分比。

在使用 tqdm(range()) 或 trange() 的基础示例中,这不是必需的,因为迭代次数已经在括号中给出。不过,有两种主要情形,添加 total=n(n 为总数)会很有帮助: 

1. 没有 len() 方法的可迭代对象: 对于没有 len() 方法、项目数量未知的生成器等,您需要手动提供 total。若未指定该参数,tqdm() 只会显示已完成的迭代次数。请注意,如果实际项目数超过指定的 total,进度条会重新开始计数。

from tqdm import tqdm
import time
import random
 
# Function generating random number between 1 and 100
def generate_random_numbers():
	while True:
	        yield random.randint(1, 100)
 
# Without total: tqdm() only shows number of iterations, does not know total
for num in tqdm(generate_random_numbers(), desc=’Random Iteration’):
	time.sleep(0.01)
	# …

该动图展示了在没有进度条时仅显示迭代次数的情况。

如果项目数量未知且未指定 total,输出会是这样。

# With total (assuming you know the desired number of iterations): tqdm() shows progress
num_iterations = 1000
for num in tqdm(generate_random_numbers(), total=num_iterations, desc="Random Iteration"):
	time.sleep(0.01)
	# …

在该动图中,指定 total 参数后,进度条重新可见。

好多了,现在进度条显示出来了!

2. 手动更新: 如果我们在函数内使用 update() 方法手动更新进度条,则需要指定 total 值以确保正确跟踪进度。update() 方法可用于响应动态变化的过程或实现自定义的进度跟踪。我们稍后会介绍。

调整视觉外观

如果我们想调整进度条元素的显示顺序,可以通过设置 bar_format 为期望的格式字符串来实现。这样就可以控制诸如百分比、已用时间、条形填充字符等元素的摆放位置。详情可参考文档。

另一项外观调整可以通过 colour 参数完成。可以使用如“green”的颜色名称,或如“#00ff00”的十六进制颜色代码。

leave 参数与“消失”相关:它决定进度条在完成后是否保持可见。设为 True,循环结束后进度条仍会保留;设为 False,则会消失。

我们通过另一个示例来看不同输出的视觉差异。以下代码将创建三个进度条:一个为默认设置,一个更改了元素顺序与颜色,另一个在此基础上设置为完成后消失。输出见下方 GIF。

from tqdm import tqdm
import time
 
# Progress bar 1: Default settings
for i in tqdm(range(300)):
	time.sleep(0.01)
 
# Progress bar 2: Customized bar format and color
for i in tqdm(range(300), bar_format='[{elapsed}<{remaining}] {n_fmt}/{total_fmt} | {l_bar}{bar} {rate_fmt}{postfix}', colour='yellow'):
	time.sleep(0.01)
 
# Progress bar 3: Customized bar format and color, leave=False
for i in tqdm(range(300), bar_format='[{elapsed}<{remaining}] {n_fmt}/{total_fmt} | {l_bar}{bar} {rate_fmt}{postfix}', colour='red', leave=False):
	time.sleep(0.01)

动图展示三种不同的进度条:1)默认设置的基础进度条;2)黄色进度条,已用时间和剩余时间位于完成百分比与进度条左侧;3)第二种的红色版本,完成后消失。

高级用法:处理更复杂的场景

现在我们已经知道如何构建简单的进度条并进行定制,可以继续学习更多高级用例。

嵌套进度条

嵌套循环是包含在其他循环中的循环。相应地,嵌套进度条是为外层循环中的每次迭代而显示的一系列进度条。要创建它们,为每个循环都包上一层 tqdm(),并为每个可迭代对象添加描述标签。

下面的代码包含三个嵌套循环,展示嵌套进度条的显示效果:

from tqdm import trange
import time
 
for i in trange(3, desc='outer loop'):
	for j in trange(2, desc='middle loop'):
   		for k in trange(6, desc='inner loop'):
	time.sleep(0.01)

一张截图展示了 Jupyter Notebook 中的三个嵌套进度条。最外层进度条跟踪外层循环,中间进度条跟踪中间层循环,最内层进度条跟踪内层循环。每个进度条都显示当前迭代、总迭代、预计剩余时间和完成百分比。其顺序与正文描述一致。

从上述最终输出中,我们可以看出不同进度条的更新模式。Tqdm 总是从最外层循环开始,直到进入最内层循环,再根据迭代进展更新对应的进度条。

假设我们有如示例所示的三个嵌套循环。在内层的首次迭代后,它会回到中间层,将其中一次迭代标记为完成,然后再回到内层。这个过程重复进行,直到中间层循环完成,随后外层进度条也会更新为完成一次迭代。

外层与中间层之间同样如此。最后,内层的最终一次运行将使中间层完成,进而完成外层的最后一次迭代。

手动更新

在以下场景中,使用 tqdm 手动更新进度条会很有用:

  • 长度未知的可迭代对象: 当处理没有定义长度的可迭代对象(如生成器、网络流)时,可根据已处理数据量或已完成操作数手动更新进度条。
  • 动态变化的过程: 如果迭代次数或每次迭代的处理时间在运行期间可能变化,手动更新可使进度条相应调整。
  • 自定义进度跟踪: 若需要更细粒度地控制进度条,可根据特定标准或事件手动更新,例如按里程碑完成情况或大型流程中各个任务的进展进行更新。
  • 与外部系统集成: 若与不便于自然跟踪进度的外部系统或库集成时,可通过手动更新来同步进度条与外部过程。

要手动更新 tqdm 进度条,务必指定 total 参数,作为预估的最大迭代次数。然后,随着代码处理每个新元素,使用 update() 方法更新进度条。update 的值应表示自上次更新以来已处理的迭代数。

假设我们预期可迭代对象最多包含 750 个元素。在该示例中,实际长度是 100 到 1000 之间的随机数,我们并不知道具体数值。我们初始化 progress_bar,将 estimated_total 设为 750。然后遍历数据,并在每个数据点处理后更新进度条。

from tqdm import tqdm
def process_data(data):
	time.sleep(0.01)        	# simulate processing data
	processed_data = data
	return processed_data
# Generate an iterable with random length between 100 and 1,000
random_length = random.randint(100, 1000)
data_list = [i for i in range(random_length)]
# Define estimated maximum number of iterations
estimated_total = 750 
# Define the progress bar using the estimated_total
progress_bar = tqdm(total=estimated_total)
# Iterating through data list of unknown length
for data in data_list:
	processed_data = process_data(data)
	progress_bar.update(1)

该动图中,进度条在 100%(750/750)时完成,但随后继续计数剩余迭代(最终到 950)。

我们低估了可迭代对象的长度,因此在达到 100% 后,输出仍继续计数。

多进程

多进程与多线程是并发执行任务以提升性能和响应速度的技术。在这些场景中,跟踪各个任务或整体并行执行的进度可能具有挑战性。Tqdm 是提供可视化反馈、监控并发操作进度的有力工具。

tqdm.contrib.concurrent 模块为多进程或多线程环境提供了专用函数,以创建进度条。这些函数负责主进程与工作进程/线程之间的同步与通信,确保进度条正确更新。它们可与 concurrent.futures API 无缝配合,使用 ProcessPoolExecutor() 或 ThreadPoolExecutor()。

以下是一个使用 tqdm.contrib.concurrent.futures 模块的示例:

import concurrent.futures
from tqdm.contrib.concurrent import process_map
 
def process_data(data):
	for i in tqdm(range(100), desc=f"Processing {data['name']}"):
	    	# Process data
	    	time.sleep(0.01)
 
if __name__ == '__main__':
	with concurrent.futures.ProcessPoolExecutor() as executor:
	    	results = process_map(process_data, [
	        	{'name': 'dataset1'},
	        	{'name': 'dataset2'},
	# …
	])

在该示例中,process_data() 函数包含一个 tqdm 进度条来跟踪其进度。process_data() 会对列表中的每个数据项并发执行。这意味着会同时显示多个进度条,每个条对应一个独立进程的进度。desc 参数根据数据集名称动态生成描述,帮助我们区分不同的进度条。

与 pandas 集成

tqdm.pandas 模块为 pandas 操作添加进度条提供了便捷方法。对于大DataFrame 上耗时的操作尤其实用,它能为任务进度提供可视化反馈。我们可以将 tqdm.pandas() 装饰器应用到任何按行或按列操作的 pandas 函数上。

首先,我们定义一个包含 100,000 行的随机 DataFrame,并调用 tqdm.pandas() 装饰器。若需自定义进度条,应在此时进行,因为 progress_apply() 和 progress_map() 不接收 tqdm() 的参数。这里我们希望为进度条命名,因此也指定 desc 参数。

import pandas as pd
import numpy as np
from tqdm import tqdm
 
df = pd.DataFrame(np.random.randint(0, 10, (100000, 6)))
 
tqdm.pandas(desc='DataFrame Operation')

现在可以将函数应用到行、列或整个 DataFrame。不要使用 apply() 或 map(),而是调用 progress_apply() 或 progress_map(),即可显示进度条。请记住,apply() 与 progress_apply() 可用于 DataFrame、行或列,而 map() 与 progress_map() 只能用于 Series 或列。例如:

# Halving each value in the DataFrame using progress_apply()
Result_apply = df.progress_apply(lambda x: x / 2)
 
# Doubling each element of the first column using progress_map()
result_map = df[0].progress_map(lambda x: x * 2)

Tqdm:常见问题与解决方法

我们来讨论一些常见的 Tqdm 问题与错误,并了解如何修复。

进度条不更新

使用 tqdm 时最常见的问题之一是进度条不更新。这通常源于缓冲问题,尤其是在Jupyter Notebook 等环境中。当输出被缓冲时,进度条可能不会立即显示或更新,从而给人一种冻结或无响应的感觉。

使用 tqdm.notebook 模块可以解决缓冲问题,并确保在 Jupyter Notebook 中正确更新进度条。该模块提供专为 Jupyter 环境设计的基于 GUI 的进度条。

此外,它还提供友好的颜色提示(蓝色:正常,绿色:完成,红色:错误/中断)。

如果我们中断了嵌套进度条示例中的代码,显示效果如下:

一张截图展示了 Jupyter Notebook 中三个嵌套进度条被中断的运行。所有已完成的条为绿色,所有被中断的条为红色。

在 Python 中使用 tqdm.notebook 的嵌套进度条,展示完成与中断条的配色方案。

另一种排查进度条不更新的有效方法是显式刷新输出流。当数据写入标准输出(例如使用 print())时,通常会先被缓冲,再发送到实际的输出设备。刷新输出流会强制 Python 解释器立即将缓冲数据发送至输出设备,确保数据及时显示或写入。

要刷新输出,请使用标准输出流的 flush() 方法。若希望输出更及时,可更频繁地刷新输出流,例如每隔几次迭代或每隔一段时间刷新一次。请注意这会带来一定开销。以下示例展示了如何将该方法融入简单的 tqdm 过程:

import sys
import time
from tqdm import tqdm
 
for i in tqdm(range(100)):
	time.sleep(0.1)
	sys.stdout.flush()  # Flush the output stream after each iteration

兼容性问题

尽管 tqdm 通常与大多数 Python 环境和库兼容,但偶尔也会出现兼容性问题或意外行为。需要留意的常见情形包括:

  • 自定义输出流: 当使用自定义输出流或将输出重定向至文件时,tqdm 可能无法按预期工作。我们需要确保所用输出流支持显示进度条所需的操作。
  • 第三方库: 在某些情况下,tqdm 可能与第三方库产生意外交互,尤其是那些自身也处理输出或进度跟踪的库。我们可以尝试禁用或修改相关第三方库的功能,看看是否能解决问题。
  • 版本兼容性: 始终建议使用兼容版本的 tqdm 与其他库。请查看库文档,了解与特定 Python 版本或其他依赖的已知兼容性问题。

遇到兼容性问题时,可以考虑以下权宜之计:

  • 降级或升级: 试用不同版本的 tqdm。
  • 修改代码: 如有必要,可调整代码以规避兼容性冲突。
  • 寻求社区帮助: 若仍无解,可以向 tqdm 社区或在线论坛求助,寻找可能的解决方案。

了解这些潜在的兼容性问题及其应对方法后,我们就能更有效地在Python 项目中排查与解决使用 tqdm 时遇到的问题。

结语

总之,Tqdm 是一个为我们提供进度条和其他有用统计信息的 Python 库,使监控与管理代码执行变得更加容易。

无论您是在遍历大型数据集、训练机器学习模型,还是执行其他耗时操作,tqdm 都提供了一种简单而强大的方式来跟踪进度,并随时了解代码状态。

若想进一步探索,欢迎查阅其他 DataCamp 的 Python 教程、 Tqdm 文档,或其 GitHub 上的源代码与 Readme。

常见问题解答

如何用 tqdm 创建一个简单的进度条?

安装并导入 tqdm 库,然后用 tqdm() 函数包装您的可迭代对象。

我能在 pandas DataFrame 或其他库中使用 tqdm 吗?

可以。您可以将 tqdm 与 pandas DataFrame 以及其他库配合使用。tqdm.pandas 模块为与 pandas 集成提供了专用函数。

我可以自定义 Tqdm 进度条的外观吗?

可以,您可以通过 Tqdm 的参数自定义条形格式、颜色、总迭代次数等。

Tqdm 进度条未正确更新,我该怎么办?

请检查是否存在缓冲问题,尤其是在 Jupyter Notebook 中。尝试使用 tqdm.notebook 或显式刷新输出。同时,确保正确使用 total 参数。


Tom Farnschläder's photo
Author
Tom Farnschläder
LinkedIn

Tom 是一名数据科学家兼技术讲师。他负责撰写并管理 DataCamp 的数据科学教程和博客文章。此前,Tom 曾在德意志电信从事数据科学工作。

主题
Python

精选 Python 课程!

课程

Python for Developers 入门

3 小时
183.9K
掌握 Python 编程基础。 无需任何先验知识!
查看详情开始课程

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。
查看更多