Reputation: 1351
Intro (Riding in on a Interstellar Office Chair)
I have a program that uses a progress bar to show the end-user if the program is working or not. This should tell the end-user how long they have to wait and if the program is still working. This question came after reading this interesting stack overflow thread: Text progress bar in terminal with block characters
Problems/Challenges
The first problem is the progress bar only works in a loop currently and just prints out the numbers in the range. This is not helpful. Why would anyone want a program with a progress bar that just shows the progress of a single loop but not what the rest of the program is doing.
The second problem is that when I have other print statements with the progress bar there are suddenly multiple progress bars printed to the command prompt instead of a single progress bar that appears to animate with each progress bar update.
How can I have the progress bar always at the bottom of the terminal with the other print statements above it?
Another challenge is I am using the Windows 7 operating-system (OS).
Code
Please check out the following example code I have for you:
import sys
import time
import threading
def progress_bar(progress):
sys.stdout.write('\r[{0}] {1}'.format('#' * int(progress/10 * 10), progress))
def doThis():
for i in range(10):
print("Doing this.")
def doThat():
for i in range(3):
print("Doing that.")
def wrapUp():
total = 2+ 2
print("Total found")
return total
if __name__ == "__main__":
print("Starting in main...")
progress_bar(1)
print("\nOther print statement here.")
print("Nice day, expensive day.")
progress_bar(3)
doThis()
progress_bar(4)
doThat()
progress_bar(5)
doThis()
doThat()
progress_bar(6)
progress_bar(7)
doThat()
doThat()
progress_bar(8)
wrapUp()
progress_bar(9)
progress_bar(10)
What the Program Prints
Starting in main...
[#] 1
Other print statement here.
Nice day, expensive day.
[###] 3Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
[####] 4Doing that.
Doing that.
Doing that.
[#####] 5Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing that.
Doing that.
Doing that.
[#######] 7Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
[########] 8Total found
[##########] 10
Upvotes: 0
Views: 1385
Reputation: 1121484
You have to do three things:
You cover the first two, somewhat, but not the third. It's probably best to encapsulate control of the console in a class that manages the progress bar too, so it can handle clearing, printing and re-displaying all in a single location:
import builtins
import math
import sys
import threading
class ProgressConsole:
def __init__(self, size, width=80, output=sys.stdout):
self.size = size
self.width = width
self.current = 0
self.output = output
# [...] and space take 3 characters, plus max width of size (log10 + 1)
self._bar_size = width - 4 - int(math.log10(size))
self._bar_step = self._bar_size / self.size
self._lock = threading.Lock()
def print(self, *message):
with self._lock:
self._clear()
builtins.print(*message, file=self.output)
self._display()
def increment(self, step=1):
with self._lock:
self.current = min(self.current + step, self.size)
self._display()
def _clear(self):
self.output.write('\r')
self.output.write(' ' * self.width)
self.output.write('\r')
def _display(self):
bar = '#' * int(round(self._bar_step * self.current))
blank = ' ' * (self._bar_size - len(bar))
self.output.write(f"\r[{bar}{blank}] {self.current}")
I included a thread lock as your sample code imports threading
, so I'm assuming you want to be able to use this in such an environment.
The above class uses a fixed width for the progress bar, and it's wiped by writing out a series of spaces before returning back to the left-most column with \r
.
I also made the bar a fixed width so it fills from left to right, rather than grow across the screen.
Then ensure you 'print' to this object:
if __name__ == "__main__":
progress_bar = ProgressConsole(10)
print = progress_bar.print # replace built-in with our own version
print("Starting in main...")
progress_bar.increment()
print("\nOther print statement here.")
print("Nice day, expensive day.")
progress_bar.increment(2)
doThis()
progress_bar.increment()
doThat()
progress_bar.increment()
doThis()
doThat()
progress_bar.increment(2)
doThat()
doThat()
progress_bar.increment()
wrapUp()
progress_bar.increment(2)
The final output for the above is then:
Starting in main...
Other print statement here.
Nice day, expensive day.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing that.
Doing that.
Doing that.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing this.
Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
Doing that.
Total found
[###########################################################################] 10
With a few random sleeps inserted, it looks like this when running:
Upvotes: 3