Python进度条

Python进度条

在Python开发中,进度条是提升用户体验的重要工具。本文将介绍几种常用的进度条实现方法,从最简单的实现到高级的自定义方案,帮助你根据不同场景选择最合适的方案。

文章目录


为什么需要进度条

当执行耗时操作时,进度条可以:

  • 让用户了解程序运行状态
  • 提供剩余时间估算
  • 提升用户体验,避免程序"假死"的错觉
  • 在调试时提供有用的执行信息

方法一:最简单的进度条实现

原理

通过 \r(回车符)回到行首,配合 end="" 防止换行,每次覆盖同一行的内容来实现进度条效果。

代码实现

python 复制代码
import time

def simple_progress_bar(total):
    for i in range(total + 1):
        # 计算百分比
        percent = 100 * i // total
        # 绘制条状图形
        bar = '#' * percent + '-' * (100 - percent)
        # \r 回到行首,end="" 防止换行
        print(f"\r[{bar}] {percent}%", end="")
        time.sleep(0.05)
    print()  # 完成后换行

if __name__ == "__main__":
    simple_progress_bar(100)

特点

  • 优点:无需任何依赖,纯Python实现
  • 缺点:功能简单,样式单一
  • 适用场景:快速原型开发,脚本工具

方法二:使用tqdm库

原理

tqdm是Python中最流行的进度条库,提供了丰富的功能和美观的界面。

代码实现

python 复制代码
import time
from tqdm import tqdm

# 直接包装迭代器
for i in tqdm(range(100), desc="处理中"):
    time.sleep(0.05)

特点

  • 优点
    • 一行代码即可使用
    • 自动计算剩余时间
    • 支持嵌套进度条
    • 丰富的自定义选项
  • 缺点:需要安装第三方库
  • 适用场景:数据处理、机器学习训练等迭代操作

方法三:自定义Spinner类

设计思路

创建一个轻量级的进度条类,实现动画扫描效果,支持Unicode字符美化显示。

核心代码

python 复制代码
import sys
import time
from rich.console import Console

class Spinner:
    """
    最小化的进度条,将单个标记在一行中来回扫描。
    动画帧预先渲染为一个帧列表。
    """

    last_frame_idx = 0  # 类变量,用于存储上一个帧索引

    def __init__(self, text: str, width: int = 7):
        self.text = text
        self.start_time = time.time()
        self.last_update = 0.0
        self.visible = False
        self.is_tty = sys.stdout.isatty()
        self.console = Console()
        self.print_mode = True  # 打印模式开关

        # 预渲染动画帧
        ascii_frames = [
            "#=        ", "=#        ", " =#       ", "  =#      ",
            "   =#     ", "    =#    ", "     =#   ", "      =#  ",
            "       =# ", "        =#", "        #=", "       #= ",
            "      #=  ", "     #=   ", "    #=    ", "   #=     ",
            "  #=      ", " #=       ",
        ]

        # Unicode支持
        self.unicode_palette = "░█"
        if self._supports_unicode():
            translation_table = str.maketrans("=#", self.unicode_palette)
            self.frames = [f.translate(translation_table) for f in ascii_frames]
            self.scan_char = self.unicode_palette[1]
        else:
            self.frames = ascii_frames
            self.scan_char = "#"

        self.frame_idx = Spinner.last_frame_idx
        self.width = len(self.frames[0]) - 2
        self.animation_len = len(self.frames[0])
        self.last_display_len = 0

    def step(self, text: str = None) -> None:
        """更新进度条状态"""
        if text is not None:
            self.text = text

        # 打印模式:直接输出新行
        if self.print_mode:
            print(f"{self.text}")
            return

        if not self.is_tty:
            return

        now = time.time()
        # 延迟显示,避免短暂任务闪烁
        if not self.visible and now - self.start_time >= 0.5:
            self.visible = True
            self.last_update = 0.0
            if self.is_tty:
                self.console.show_cursor(False)

        # 限制更新频率
        if not self.visible or now - self.last_update < 0.1:
            return

        self.last_update = now
        frame_str = self._next_frame()

        # 处理行宽和文本截断
        max_spinner_width = self.console.width - 2
        current_text_payload = f" {self.text}"
        line_to_display = f"{frame_str}{current_text_payload}"

        if len(line_to_display) > max_spinner_width:
            line_to_display = line_to_display[:max_spinner_width]

        len_line_to_display = len(line_to_display)
        padding_to_clear = " " * max(0, self.last_display_len - len_line_to_display)

        # 写入进度条
        sys.stdout.write(f"\r{line_to_display}{padding_to_clear}")
        self.last_display_len = len_line_to_display

        # 定位光标到扫描字符
        scan_char_abs_pos = frame_str.find(self.scan_char)
        total_chars_written = len_line_to_display + len(padding_to_clear)
        num_backspaces = total_chars_written - scan_char_abs_pos
        sys.stdout.write("\b" * num_backspaces)
        sys.stdout.flush()

    def end(self) -> None:
        """清理进度条"""
        if self.visible and self.is_tty:
            clear_len = self.last_display_len
            sys.stdout.write("\r" + " " * clear_len + "\r")
            sys.stdout.flush()
            self.console.show_cursor(True)
        self.visible = False

使用示例

python 复制代码
spinner = Spinner("正在运行进度条...")
try:
    for _ in range(100):
        time.sleep(0.15)
        spinner.step()
    print("成功!")
except KeyboardInterrupt:
    print("\n用户中断。")
finally:
    spinner.end()

特点

  • 优点
    • 轻量级,无外部依赖
    • 支持Unicode美化
    • 智能延迟显示
    • 自动处理终端宽度
  • 适用场景:命令行工具、后台任务

方法四:线程安全的WaitingSpinner

设计思路

在Spinner基础上增加线程支持,实现后台自动更新,适合长时间运行的等待任务。

核心代码

python 复制代码
import threading
import time
from base_util.spin_progress import Spinner

class WaitingSpinner:
    """可在后台安全启动/停止的进度条。"""

    def __init__(self, text: str = "等待 LLM 响应", delay: float = 0.15):
        self.spinner = Spinner(text)
        self.delay = delay
        self._stop_event = threading.Event()
        self._thread = threading.Thread(target=self._spin, daemon=True)

    def _spin(self):
        """后台线程执行函数"""
        while not self._stop_event.is_set():
            self.spinner.step()
            time.sleep(self.delay)
        self.spinner.end()

    def start(self):
        """在后台线程中启动进度条。"""
        if not self._thread.is_alive():
            self._thread.start()

    def stop(self):
        """请求进度条停止,并短暂等待线程退出。"""
        self._stop_event.set()
        if self._thread.is_alive():
            self._thread.join(timeout=self.delay)
        self.spinner.end()

    # 支持上下文管理器
    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()

使用示例

方式一:手动控制
python 复制代码
spinner = WaitingSpinner()
spinner.start()

# 模拟耗时任务
time.sleep(3)

spinner.stop()
print("任务完成")
方式二:上下文管理器(推荐)
python 复制代码
with WaitingSpinner("正在连接服务器。。。"):
    # 执行耗时操作
    time.sleep(2)

print("自动清理完成")

特点

  • 优点
    • 线程安全,适合异步任务
    • 支持上下文管理器
    • 自动异常处理
    • 后台自动更新
  • 适用场景:网络请求、文件IO、API调用等异步操作

进阶功能:打印模式与动画模式

打印模式

每次更新都打印新行,适合需要保留历史记录的场景。

python 复制代码
spinner = Spinner("正在处理...")
spinner.print_mode = True  # 启用打印模式

for i in range(1, 11):
    time.sleep(0.3)
    spinner.step(f"步骤 {i}/10")

spinner.end()

动画模式

默认模式,在同一行来回扫描,提供流畅的动画效果。

python 复制代码
spinner = Spinner("正在加载...")
spinner.print_mode = False  # 动画模式

for i in range(50):
    time.sleep(0.1)
    spinner.step()

spinner.end()

模式切换

python 复制代码
class Spinner:
    def __init__(self, text: str, width: int = 7):
        # ...
        self.print_mode = True  # 默认打印模式

    def step(self, text: str = None) -> None:
        if text is not None:
            self.text = text

        # 打印模式:直接输出新行
        if self.print_mode:
            print(f"{self.text}")
            return

        # 动画模式:执行扫描动画
        # ...动画逻辑

方案对比与选择建议

对比表格

方案 复杂度 依赖 性能 美观度 适用场景
简单进度条 ⭐⭐ 快速原型
tqdm tqdm ⭐⭐⭐⭐ 数据处理
Spinner ⭐⭐⭐ rich ⭐⭐⭐ 命令行工具
WaitingSpinner ⭐⭐⭐⭐ rich ⭐⭐⭐ 异步任务

选择建议

  1. 快速开发 → 使用简单进度条或tqdm
  2. 生产环境CLI工具 → 使用Spinner类
  3. 异步/后台任务 → 使用WaitingSpinner
  4. 数据分析/机器学习 → 使用tqdm
  5. 需要详细日志 → 使用打印模式
  6. 需要美观动画 → 使用动画模式

最佳实践

1. 异常处理

python 复制代码
spinner = Spinner("处理中...")
try:
    # 执行任务
    risky_operation()
except Exception as e:
    print(f"错误: {e}")
finally:
    spinner.end()  # 确保清理

2. 上下文管理器

python 复制代码
# 自动管理生命周期,即使发生异常也能正确清理
with WaitingSpinner("加载中..."):
    perform_task()

3. 避免过度更新

python 复制代码
# 限制更新频率,避免性能问题
if now - self.last_update < 0.1:  # 100ms最小间隔
    return

4. 非TTY环境检测

python 复制代码
# 在管道或重定向时禁用动画
if not sys.stdout.isatty():
    return

总结

本文介绍了四种Python进度条实现方法:

  1. 最简单的进度条:零依赖,适合快速开发
  2. tqdm库:功能丰富,适合迭代任务
  3. Spinner类:轻量美观,适合CLI工具
  4. WaitingSpinner:线程安全,适合异步任务

选择合适的进度条方案,可以显著提升用户体验和程序可维护性。记住:好的进度条不仅要美观,更要实用!


参考资料


作者注:本文所有代码示例均来自实际项目,经过充分测试,可直接用于生产环境。

相关推荐
l1t2 小时前
四种python工具包用SQL查询csv和parquet文件的方法比较
大数据·python·sql
清水白石0082 小时前
Python 并发三剑客:多线程、多进程与协程的实战抉择
java·服务器·python
2301_793804692 小时前
更优雅的测试:Pytest框架入门
jvm·数据库·python
小一梦2 小时前
宝塔面板单域名部署多个 Vue 项目:从路径冲突到完美共存
服务器·javascript·vue.js
IMPYLH2 小时前
Linux 的 b2sum 命令
linux·运维·服务器·bash
dinl_vin2 小时前
python:常用的基础工具包
开发语言·python
wefly20172 小时前
无需安装、开箱即用!m3u8live.cn 在线 HLS 播放器,调试直播流效率翻倍
前端·后端·python·前端开发工具·后端开发工具
celeste03102 小时前
Redis Summary
linux·运维·服务器·redis·笔记
Sylvan.C3 小时前
Windows+Ubuntu 双系统安装超详细保姆级教程2026,包括系统安装、英伟达独显驱动安装以及双系统时间同步的所有过程
linux·运维·ubuntu