Simple python progress bar
Posted on Fri 31 July 2015 in Python
I'm big on beautiful command line tools, I often launch htop
just to please my eyes. tig output on a large repo with hundreds of intertwined branches is like an work of art to me.
When working on some command line tool or script I try make it beautiful. Today I'm going to explain how to create simple and beautiful command line progress bar in python.
So let's start, here's how wget
progress bar looks like:
[############################# ]
Here we have [
and ]
indicating the left and right border of progress bar, progress is denoted by sequence of #
characters, and remaining work is just a bunch of spaces.
Let's initialize our progress bar:
class ProgressBar:
def __init__(
self, start_value, total_value, width, start_border='[',
end_border=']', progress_char='#', remaining_char='-'
):
self._start_value = start_value
self._total_value = total_value
self._width = width
self._start_border = start_border
self._end_border = end_border
self._progress_char = progress_char
self._remaining_char = remaining_char
self._progress = self.set_progress(self._start_value)
Here we have following parameters:
start_value
- Initial value of progress
total_value
- Value for 100% progress
width
- progress bar width
start_border
, end_border
- indicate the left and right borders of progress bar
progress_char
- single-character string to represent current progress
remaining_char
- single-character string to represent remaining progress
The variable self._progress
will hold the current number of progress chars. Next we'll need to define set_progress
method. That's simple:
def set_progress(self, value):
return round(self._width * value / self._total_value)
I didn't really knew how to make a progress bar to appear as a single line, without printing new line for each step, eg:
[######################## ]
[############################# ]
[################################## ]
and so on. But, as it turns out, it's pretty simple. \r
- the carriage return character is what we need here. Here's what it does:
In [1]: import sys
In [2]: lines = ['this will be removed', '\r', 'this will be displayed']
In [3]: for line in lines:
....: sys.stdout.write(line)
....:
this will be displayed
If you ever used a typewriter, it should be familiar :)
So let's define a progress
method which will actually display something - update progress:
def progress(self, value):
self._progress = self.set_progress(value)
sys.stdout.write("\r{}{}{}{}".format(
self._start_border,
self._progress_char * self._progress,
self._remaining_char * (self._width - self._progress),
self._end_border)
)
sys.stdout.flush()
Here we just calculate the number of progress characters and remaining characters, and then rewrite the current progress line with the new one. Flushing is important to make sure progress updates with each step.
Let's try how it works:
import time
pb = ProgressBar(1, 100, 50)
for i in range(100):
pb.progress(i)
time.sleep(0.1)
Here's some asciinema demo:
Let's add some helper methods for working with generators:
def do_with_progress(self, gen):
for value in gen:
self.progress(value)
Then we can have something like:
def sleeper():
for i in range(100):
yield i
time.sleep(0.1)
pb.do_with_progress(sleeper())
You can find the code here