Python tqdm 实践:构建文件处理与写入操作的进度条

聖光之護
发布: 2025-07-09 21:24:21
原创
908人浏览过

Python tqdm 实践:构建文件处理与写入操作的进度条

本文深入探讨了如何利用 Python tqdm 库为文件处理和写入操作添加进度条。不同于常见的下载进度追踪,我们将展示一种策略,通过监控文件级别的处理完成情况来更新进度条,特别适用于一次性读取和写入整个文件内容的场景。文章将提供详细的代码示例和实现步骤,帮助开发者在文件加密、转换等任务中实现直观的进度反馈。

tqdm 库简介与常见应用

tqdm 是一个功能强大且易于使用的 python 库,用于在循环迭代过程中显示进度条。它的名字来源于阿拉伯语 taqaddum (تقدم) 意为“进步”,也恰好是“我很快乐” (i'm happy) 的缩写。tqdm 最常见的应用场景是文件下载、数据处理或任何涉及迭代大量任务的场景。

例如,在文件下载过程中,tqdm 可以通过迭代数据块并更新已接收的字节数来实时显示下载进度:

import requests
from tqdm import tqdm

url = 'https://wordnetcode.princeton.edu/2.1/WNsnsmap-2.1.tar.gz'
filename = url.split('/')[-1]
resp = requests.get(url, stream=True)
# 初始化进度条,total 为文件总大小,unit='B' 表示单位是字节
pbar = tqdm(desc=filename, total=int(resp.headers.get('content-length', 0)),
            unit='B', unit_scale=True, unit_divisor=1024)

with open(filename, 'wb') as f:
    for data in resp.iter_content(chunk_size=1024): # 按块迭代内容
        f.write(data)
        pbar.update(len(data)) # 每次写入一块数据后更新进度条

pbar.close() # 关闭进度条
登录后复制

在这个例子中,iter_content(chunk_size=1024) 使得数据以小块的形式流式传输,每次写入一块后,我们都可以通过 pbar.update(len(data)) 来精确更新已写入的字节数,从而实现实时的进度反馈。

文件处理场景下的挑战

然而,当面对本地文件处理任务,例如读取整个文件内容进行加密或转换,然后一次性写入处理后的数据时,传统的按块更新进度条的方法就不那么直接适用了。

考虑以下场景:

立即学习Python免费学习笔记(深入)”;

import os
from base64 import b85encode # 假设 b85encode 是你的处理逻辑

input_dir = "path/to/your/files" # 替换为你的输入目录

for filefolder, dirs, files in os.walk(input_dir):
    for filename in files:
        file_path = os.path.join(filefolder, filename)
        print(f"正在加密 {filename}..")
        try:
            with open(file_path, 'rb') as encryptingfile:
                # 一次性读取整个文件内容
                original_bytes = encryptingfile.read()
                # 进行加密处理,例如 Base85 编码
                b85_bytes = b85encode(original_bytes)

            # 一次性写入处理后的所有字节
            with open(file_path, 'wb') as output_file:
                output_file.write(b85_bytes)

            # 问题:在哪里调用 pbar.update() 来显示 output_file.write(b85_bytes) 的进度?
            # 因为 write() 是一个原子操作,它不提供内部的字节写入进度。

        except PermissionError:
            print(f"\r跳过: {filename} (权限不足)\n")
        except Exception as e:
            print(f"\r处理 {filename} 时发生错误: {e}\n")
登录后复制

在这个代码块中,encryptingfile.read() 一次性将整个文件读入内存,b85encode() 对整个内容进行处理,而 output_file.write(b85_bytes) 也是一次性将所有处理后的字节写入文件。write() 方法本身不提供分块写入的接口,因此我们无法像下载示例那样,在每次写入一小块数据后更新进度条。我们需要一种不同的策略来追踪这类文件处理任务的进度。

解决方案:基于文件完成度的进度追踪

对于上述挑战,一种有效的解决方案是:将进度条的更新粒度从“字节块”提升到“文件完成”。也就是说,我们不追踪单个文件内部的字节写入进度,而是追踪整个文件集合中每个文件处理完成的进度。当一个文件被完整处理并写入完成后,我们就更新进度条,增加该文件的大小到已处理总量中。

这种方法的核心思想是:

  1. 首先计算所有待处理文件的总大小,作为 tqdm 进度条的 total 值。
  2. 在循环处理每个文件时,当该文件的所有操作(读取、处理、写入)完成后,通知 tqdm 该文件已处理完毕。

下面是具体的实现步骤和代码:

步骤一:计算总处理量

我们需要一个函数来遍历指定文件夹下的所有文件,并计算它们的总大小。这将作为 tqdm 进度条的 total 参数。

import os
from tqdm import tqdm
import time # 用于模拟文件处理耗时
from base64 import b85encode, b85decode # 模拟加密/解密

def iter_files(folder_path):
    """
    遍历指定文件夹及其子文件夹中的所有文件,并返回文件大小和完整路径。
    """
    for root, _, files in os.walk(folder_path):
        for file_name in files:
            file_full_path = os.path.join(root, file_name)
            try:
                # 确保文件存在且可访问,获取其大小
                if os.path.exists(file_full_path) and os.path.isfile(file_full_path):
                    yield os.path.getsize(file_full_path), file_full_path
            except Exception as e:
                print(f"无法获取文件大小或访问文件: {file_full_path}, 错误: {e}")
                continue
登录后复制

iter_files 函数是一个生成器,它会逐个返回每个文件的大小和路径。通过 sum(s for s, _ in iter_files(folder_path)) 就可以方便地计算出所有文件的总大小。

步骤二:封装进度更新逻辑

接下来,我们创建一个函数来初始化 tqdm 进度条,并为每个文件提供一个回调函数,当文件处理完成后调用此回调来更新进度。

def iter_with_progress(folder_path, description="处理文件"):
    """
    初始化一个 tqdm 进度条,并为每个文件提供一个更新进度的回调函数。
    """
    # 计算所有文件的总大小作为进度条的总量
    total_size = sum(s for s, _ in iter_files(folder_path))

    # 初始化 tqdm 进度条
    progress_bar = tqdm(unit='B',
                        total=total_size,
                        unit_scale=True,
                        unit_divisor=1024,
                        desc=description)

    # 遍历文件,并为每个文件提供一个更新进度的回调
    for file_size, file_path in iter_files(folder_path):
        # 使用 lambda 表达式创建一个闭包,当文件处理完成后调用它来更新进度条
        update_callback = lambda size=file_size: progress_bar.update(size)
        yield update_callback, file_size, file_path

    # 在所有文件迭代完毕后,确保进度条关闭
    # 注意:如果循环提前终止,可能需要手动调用 progress_bar.close()
    progress_bar.close()
登录后复制

iter_with_progress 也是一个生成器。它首先计算出所有文件的总大小并初始化 tqdm 实例。然后,在每次迭代中,它会 yield 一个元组,其中包含:

  • update_callback: 一个函数,调用它即可将当前文件的大小添加到进度条的已完成量中。
  • file_size: 当前文件的大小。
  • file_path: 当前文件的完整路径。

步骤三:集成到文件处理流程

现在,我们可以将上述函数集成到实际的文件处理(例如加密/解密)流程中:

# 示例:模拟文件加密并更新进度条
# 请将 'path/to/your/input/directory' 替换为你要处理的文件夹路径
input_directory = 'path/to/your/input/directory'

# 确保输入目录存在
if not os.path.isdir(input_directory):
    print(f"错误:目录 '{input_directory}' 不存在。请替换为有效路径。")
else:
    print(f"开始处理目录: {input_directory}")
    for update_progress_callback, file_size, file_path in iter_with_progress(input_directory, description="文件处理"):
        try:
            print(f"\n正在处理文件: {os.path.basename(file_path)} ({file_size} Bytes)")

            # 模拟文件读取和加密操作
            with open(file_path, 'rb') as f_read:
                original_bytes = f_read.read()
                # 假设 b85encode 是你的加密逻辑
                processed_bytes = b85encode(original_bytes) # 或 b85decode(original_bytes) 进行解密

            # 模拟文件写入操作
            # 注意:这里直接覆盖原文件,实际应用中可能写入新文件或进行备份
            with open(file_path, 'wb') as f_write:
                f_write.write(processed_bytes)

            # 文件处理完成后,调用回调函数更新进度条
            update_progress_callback()

        except PermissionError:
            print(f"跳过文件 (权限不足): {os.path.basename(file_path)}")
        except Exception as e:
            print(f"处理文件 {os.path.basename(file_path)} 时发生错误: {e}")

    print("\n所有文件处理完毕。")
登录后复制

在这个集成示例中,for 循环从 iter_with_progress 中获取每个文件的信息和更新回调。在 try 块中,我们执行实际的文件读取、处理(例如 b85encode)和写入操作。一旦这些操作成功完成,我们就调用 update_progress_callback(),这将通知 tqdm 进度条当前文件已处理完成,并更新已完成的总字节数。

tqdm 参数详解

在上面的示例中,我们使用了 tqdm 的几个关键参数:

  • unit='B': 指定进度条的单位为字节(Bytes)。
  • total=total_size: 设置进度条的总量,这里是所有待处理文件的总大小。
  • unit_scale=True: 启用单位自动缩放,例如,当字节数达到 KB、MB、GB 时,会自动显示相应的单位。
  • unit_divisor=1024: 设置单位缩放的除数,通常是 1024 (KB, MB) 或 1000 (千, 百万)。对于文件大小,1024 更常见。
  • desc="文件处理": 设置进度条的描述信息,会显示在进度条的前面。

注意事项与进阶思考

  1. 适用场景与局限性:

    • 此方法非常适合处理文件夹中多个文件的场景,它能清晰地展示整个批处理任务的进度。
    • 局限性: 对于单个文件非常大,且其内部处理(如加密)本身耗时很长的情况,此方法在文件处理完成前不会更新进度条。如果需要追踪单个大文件内部的实时进度,则需要将文件内容分块读取、处理和写入,并在每次处理完一个块后更新 tqdm。例如,可以使用 functools.partial 或自定义包装器来包装 file.write,使其在每次写入时报告进度。
  2. 内存管理:

    • 在示例中,我们使用了 encryptingfile.read() 一次性读取整个文件内容到内存。对于非常大的文件(例如几 GB 甚至更大),这可能导致内存溢出。
    • 建议: 对于大文件,应考虑采用分块读写的方式,即使是加密/解密操作,也可以设计为流式处理。例如,读取一个块,处理一个块,写入一个块,这样可以显著降低内存消耗。
  3. 错误处理:

    • 在实际应用中,务必加入健壮的错误处理机制。示例中包含了 PermissionError 和通用的 Exception 捕获,但在生产环境中,可能需要更细致的错误类型判断和日志记录。
  4. 进度条的关闭:

    • tqdm 实例在 total 达到后会自动关闭。在 iter_with_progress 函数中,我们显式调用了 progress_bar.close() 来确保在生成器迭代完毕后,进度条资源被正确释放。

总结

通过本文的介绍,我们学习了如何利用 tqdm 库在 Python 中为文件处理和写入操作添加直观的进度条。与传统的下载进度追踪不同,我们采用了一种基于文件完成度的策略,通过计算所有待处理文件的总大小,并在每个文件处理完成后更新进度条,从而有效解决了 file.write() 操作原子性带来的进度追踪难题。掌握这种方法,将有助于你在开发文件批量处理工具时提供更好的用户体验。

以上就是Python tqdm 实践:构建文件处理与写入操作的进度条的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号