11Sep/094
Python Animated Progress Bar
I wanted to be able to have an animated progress bar to be displayed in a terminal for my current Python project. There are already various implementations on the Internet of something like what I wanted, but I couldn't find one that was animated. So for the sake of practice, I decided to write my own. In order to be able to have the bar animated, and still allow the program to get other work done, I had to create a seperate thread to manage the status bar.
Code for the bar, (and also sample code to create a demo) are below.
import time import sys import os import threading """ Display an animated statusbar, with progress and percentage ( items-completed/items-total ) displayed below the statusbar. Seperate thread is used to display the spinning "icon." In order to stop the statusbar thread early, calling thread can use join() example output created by StatusBar thread: [===============\--------------] 30/60 50% Written by chi (aka chi42) from 42gems.com-- 11 Sept, 2009 Copyright (C) 2009 chi (from 42gems.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Please see http://www.gnu.org/licenses/ for a copy of the license. """ class StatusBar(threading.Thread): # class variables: # max: number of total items to be completed # pos: number of completed items # inc: amount of items to increment completed 'pos' by # (shared resource) # comp: amount of '=' to display in the progress bar # running: whether or not the statusbar is running def __init__(self, pos=0, max=100): threading.Thread.__init__(self) self.pos = pos self.max = max self.busy_char = '|' self.running = 0 self.inc = 0 self.__getsize() self.comp = int(float(self.pos) / self.max * self.columns) self.inc_lock = threading.Lock() # find number of columns in terminal def __getsize(self): rows, columns = os.popen('stty size', 'r').read().split() if int(columns) > 80: self.columns = 80 - 2 else: self.columns = int(columns) - 2 return # redraw progress bar and all numerial values def __print(self): self.__getsize() sys.stdout.write('\x1b[1G') sys.stdout.write('[' + '=' * self.comp + self.busy_char + \ '-'*(self.columns - self.comp - 1) + ']' ) sys.stdout.write('\n\x1b[0K' + str(self.pos) + \ '/' + str(self.max) + '\t' + \ str( round(float(self.pos) / self.max * 100, 2)) + '%') sys.stdout.write('\x1b[1A\x1b[' + \ str(self.comp + 2) + 'G') sys.stdout.flush() return # run the thread def run(self): global busy_chars, inteval busy_chars = ['|','/','-','\\'] interval = 0.3 self.running = 1 self.__print() while 1: # loop and display the busy spinning icon for c in busy_chars: self.busy_char = c sys.stdout.write(c + '\x1b[1D') sys.stdout.flush() time.sleep(interval) self.inc_lock.acquire() if self.inc: if (self.pos + self.inc) >= self.max: self.inc_lock.release() self.pos = self.max self.comp = self.columns self.busy_char = '' self.__print() sys.stdout.write('\n\n') self.running = 0 return 0 else: self.pos += self.inc self.inc = 0 self.inc_lock.release() self.comp = int(float(self.pos) / self.max \ * self.columns) self.__print() else: self.inc_lock.release() return 1 # increment number of completed items used by calling thread def increment(self): if self.running: self.inc_lock.acquire() self.inc += 1 self.inc_lock.release() return 0 else: return 1
Annnd the demo code:
#!/usr/bin/python import statusbar import time import os print '\n' min, max = 0, 80 inc_sleep = 3 bar = statusbar.StatusBar(min, max) rows, columns = os.popen('stty size', 'r').read().split() print 'columns in screen: ', columns bar.start() while 1: time.sleep(inc_sleep) if bar.increment(): break print 'done!'
Categories
- Computers (27)
- Daily Log (18)
- Gadgets (5)
- Linux (34)
- Literature (20)
- Out Doors (23)
- Polyphasic Sleep (6)
- Programming (31)
- Random Stuff (47)
- School (43)
- Site News (26)
- Software (16)
- Stocks (6)
- Uncategorized (3)
- work (4)
Archives
- February 2016 (1)
- August 2014 (3)
- October 2013 (1)
- June 2013 (1)
- May 2013 (4)
- May 2012 (1)
- March 2012 (3)
- August 2011 (1)
- June 2011 (1)
- January 2011 (2)
- December 2010 (2)
- October 2010 (1)
- September 2010 (2)
- May 2010 (2)
- April 2010 (3)
- January 2010 (4)
- December 2009 (1)
- November 2009 (4)
- October 2009 (2)
- September 2009 (5)
- August 2009 (6)
- June 2009 (1)
- May 2009 (5)
- April 2009 (4)
- March 2009 (9)
- February 2009 (7)
- January 2009 (6)
- December 2008 (6)
- November 2008 (3)
- October 2008 (7)
- September 2008 (6)
- August 2008 (11)
- July 2008 (25)
- June 2008 (12)
- May 2008 (7)
- April 2008 (12)
- March 2008 (5)
- February 2008 (5)
- January 2008 (7)
- December 2007 (2)
- November 2007 (6)
- October 2007 (4)
- August 2007 (3)
- July 2007 (6)
- June 2007 (2)
- May 2007 (10)
- April 2007 (7)
Comments
- M. Robbins on Miyata Alumicross
- paula on Miyata Alumicross
- Tobias on Miyata Alumicross
- Vitaliy on Why I Hate Remote Working
- renato on My Old Trek 820
December 21st, 2010 - 14:55
I\’m new to the threading package, so I\’m confused about how to use join to stop the bar. I wrote this patch implementing StatusBar::stop():
55,63d54
< self._stop = threading.Event()
<
< def stop(self):
< self.running = 0
< self._stop.set()
<
< def stopped(self):
< return self._stop.isSet()
<
97c88
< while not self.stopped():
—
> while 1:
Call StatusBar::stop() to exit early. I use this inside of an \"except KeyboardInterrupt\" when the user wishes to stop a long running operation.
December 23rd, 2010 - 11:51
Hey Pete,
looking at your patch, it should work. (Although, I just noticed a slight bug in my code where self.running is not protected by a mutex in increment(), it shouldn’t affect you.)
Once you call bar.stop() you can just call bar.join(). Although, in your stop function, you may also want to reposition the cursor to a new line.
July 16th, 2011 - 16:51
I think “global busy_chars, inteval” should be “global busy_chars, interval”.
Nice code!
=)
July 26th, 2011 - 15:57
Ctrl-C crashes it