Progress Tracking in Python
last modified April 1, 2025
Tracking progress is essential for long-running operations in Python. This tutorial covers various methods to implement progress tracking, from simple console indicators to advanced GUI progress bars.
We'll explore multiple approaches including the popular tqdm library, manual progress bars, callback-based tracking, and integration with different Python frameworks. Each method has its strengths for different use cases.
Basic Console Progress
Simple progress tracking can be implemented with print statements.
import time
total = 100
for i in range(total):
# Process item here
time.sleep(0.1) # Simulate work
# Update progress
progress = (i + 1) / total * 100
print(f"\rProgress: {progress:.1f}%", end="")
print("\nDone!")
This example shows the simplest way to track progress in console applications. The \r carriage return moves the cursor to the start of the line, allowing progress updates on the same line. The end="" parameter prevents newlines.
While basic, this method works everywhere and requires no dependencies. It's ideal for simple scripts where you just need to show completion percentage. The sleep call simulates processing time for demonstration purposes.
Using tqdm for Progress Bars
The tqdm library provides advanced progress bars with minimal code.
from tqdm import tqdm
import time
# Basic progress bar
for i in tqdm(range(100)):
time.sleep(0.02) # Simulate work
# With description and unit
items = ["apple", "banana", "cherry", "date"]
for item in tqdm(items, desc="Processing", unit="item"):
time.sleep(0.5)
tqdm automatically displays a progress bar with estimated time remaining, iterations per second, and more. The desc parameter adds a description, while unit specifies what's being processed.
The library handles all formatting and updates automatically. It works in Jupyter notebooks and regular consoles. tqdm is the go-to solution for most progress tracking needs in Python.
Manual Progress Bar
For custom requirements, you can build your own progress bar.
import sys
import time
def draw_progress(progress, width=50):
filled = int(width * progress)
bar = "[" + "=" * filled + " " * (width - filled) + "]"
sys.stdout.write(f"\r{bar} {progress*100:.1f}%")
sys.stdout.flush()
total = 200
for i in range(total):
time.sleep(0.02)
draw_progress((i + 1) / total)
print("\nComplete!")
This implementation gives full control over the progress bar appearance. The draw_progress function creates a bar of specified width, filled proportionally to the current progress.
Using sys.stdout.write with flush ensures smooth updates. This approach is useful when you need custom formatting or when external libraries aren't available.
Progress Tracking for File Operations
Track progress during file operations using callbacks.
import shutil
import os
def copy_with_progress(src, dst):
total = os.path.getsize(src)
copied = 0
def callback(chunk):
nonlocal copied
copied += len(chunk)
progress = copied / total * 100
print(f"\rCopying: {progress:.1f}%", end="")
shutil.copyfileobj(
open(src, "rb"),
open(dst, "wb"),
callback=callback
)
print("\nDone!")
copy_with_progress("large_file.dat", "copy.dat")
This example demonstrates progress tracking during file copy operations. The callback function receives chunks of data being copied and updates the progress display accordingly.
The method works with any file-like object, making it versatile for network transfers or custom data processing pipelines. The nonlocal keyword allows modifying the copied counter from the nested function.
Threaded Progress Updates
For background tasks, use threads to update progress separately.
import threading
import time
import random
class ProgressTracker:
def __init__(self, total):
self.progress = 0
self.total = total
self.running = True
def update(self):
while self.running and self.progress < self.total:
self.progress += random.randint(1, 5)
time.sleep(0.1)
def display(self):
while self.running and self.progress < self.total:
pct = self.progress / self.total * 100
print(f"\rProgress: {pct:.1f}%", end="")
time.sleep(0.05)
print("\nDone!")
tracker = ProgressTracker(100)
worker = threading.Thread(target=tracker.update)
display = threading.Thread(target=tracker.display)
worker.start()
display.start()
worker.join()
tracker.running = False
display.join()
This pattern separates the work and progress display into different threads. The ProgressTracker class coordinates between them with a shared progress variable.
The running flag ensures clean shutdown. This approach is ideal for GUI applications or when the work being done can't easily report progress itself.
Progress Tracking in Jupyter
Jupyter notebooks support rich progress displays.
from IPython.display import display
import ipywidgets as widgets
import time
progress = widgets.FloatProgress(
value=0, min=0, max=100, description="Loading:"
)
display(progress)
for i in range(100):
time.sleep(0.05)
progress.value = i + 1
IPython provides special widgets for interactive progress tracking. The FloatProgress widget creates a visual bar that updates in real-time.
This method creates professional-looking progress indicators in notebooks. The widgets integrate seamlessly with Jupyter's display system and support additional features like styling and multiple progress bars.
Progress Tracking with Multiprocessing
Track progress across multiple processes using shared memory.
import multiprocessing
import time
def worker(shared_progress, total_work):
for i in range(total_work):
time.sleep(0.1)
with shared_progress.get_lock():
shared_progress.value += 1
def display_progress(shared_progress, total):
while True:
with shared_progress.get_lock():
current = shared_progress.value
progress = current / total * 100
print(f"\rProgress: {progress:.1f}%", end="")
if current >= total:
break
time.sleep(0.1)
if __name__ == "__main__":
total_tasks = 100
progress = multiprocessing.Value("i", 0)
processes = []
for _ in range(4):
p = multiprocessing.Process(
target=worker,
args=(progress, total_tasks//4)
)
processes.append(p)
p.start()
display_progress(progress, total_tasks)
for p in processes:
p.join()
print("\nAll tasks completed!")
This example demonstrates progress tracking across multiple worker processes. A shared Value coordinates progress updates between processes.
The get_lock() context manager ensures thread-safe updates. This pattern is essential for CPU-bound tasks distributed across multiple cores while still providing progress feedback.
GUI Progress Bars with Tkinter
Create graphical progress bars for desktop applications.
import tkinter as tk
from tkinter import ttk
import threading
import time
class ProgressApp:
def __init__(self, root):
self.root = root
self.progress = ttk.Progressbar(
root, orient="horizontal",
length=300, mode="determinate"
)
self.progress.pack(pady=20)
self.button = tk.Button(
root, text="Start Task",
command=self.start_task
)
self.button.pack()
def start_task(self):
self.button.config(state="disabled")
thread = threading.Thread(target=self.run_task)
thread.start()
self.monitor(thread)
def run_task(self):
for i in range(100):
time.sleep(0.05)
self.progress["value"] = i + 1
def monitor(self, thread):
if thread.is_alive():
self.root.after(100, lambda: self.monitor(thread))
else:
self.button.config(state="normal")
root = tk.Tk()
app = ProgressApp(root)
root.mainloop()
This Tkinter application shows a professional progress bar that updates during a background task. The threading prevents GUI freezing during long operations.
The monitor method periodically checks the thread status. This pattern ensures responsive UIs while providing visual feedback for operations that take time to complete.
Web Progress Tracking with Flask
Implement progress tracking in web applications.
from flask import Flask, jsonify, render_template_string
import time
import threading
app = Flask(__name__)
progress_data = {"value": 0, "total": 100}
def long_running_task():
for i in range(progress_data["total"]):
time.sleep(0.1)
progress_data["value"] = i + 1
@app.route("/")
def index():
return render_template_string("""
<h1>Progress Demo</h1>
<div id="progress">0%</div>
<button onclick="startTask()">Start</button>
<script>
function startTask() {
fetch("/start").then(() => pollProgress());
}
function pollProgress() {
fetch("/progress").then(r => r.json())
.then(data => {
document.getElementById("progress").innerHTML =
data.value + "%";
if (data.value < 100) {
setTimeout(pollProgress, 500);
}
});
}
</script>
""")
@app.route("/start")
def start():
thread = threading.Thread(target=long_running_task)
thread.start()
return "Started"
@app.route("/progress")
def progress():
return jsonify({
"value": progress_data["value"],
"total": progress_data["total"]
})
if __name__ == "__main__":
app.run(threaded=True)
This Flask application demonstrates web-based progress tracking. The frontend polls the backend for progress updates while the task runs in a background thread.
The template_string shows a minimal HTML interface. In production, you'd separate templates and add error handling. The threaded=True option allows Flask to handle concurrent requests.
Best Practices
- Choose the right method: Use tqdm for CLI, widgets for Jupyter
- Update frequency: Don't update too often (100ms minimum)
- Thread safety: Protect shared progress variables with locks
- Error handling: Ensure progress stops on errors
- User experience: Provide clear labels and completion messages
Source References
Author
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.