3

I'm rewritting a C# Console application in Python, and I'd like to port an indeterminate console-based progress bar class I wrote.

I have examples of creating determinate progress bars using text, but I'm not sure how I would do an indeterminate one. I'm assuming I would need threading of some sort. Thanks for your help!

This is the class:

public class Progress {
    String _status = "";
    Thread t = null;

    public Progress(String status) {
        _status = status;
    }

    public Progress Start() {
        t = new Thread(() => {
            Console.Write(_status + "    ");

            while (true) {
                Thread.Sleep(300);
                Console.Write("\r" + _status + "    ");
                Thread.Sleep(300);
                Console.Write("\r" + _status + " .  ");
                Thread.Sleep(300);
                Console.Write("\r" + _status + " .. ");
                Thread.Sleep(300);
                Console.Write("\r" + _status + " ...");
            }
        });

        t.Start();

        return this;
    }

    public void Stop(Boolean appendLine = false) {
        t.Abort();
        Console.Write("\r" + _status + " ... ");
        if (appendLine)
            Console.WriteLine();
    }

}

(P.S. Feel free to take that Progress class)

Chris Laplante
  • 29,338
  • 17
  • 103
  • 134
  • As a general rule, you shouldn't abort threads. See http://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort. – Jim Mischel Mar 24 '11 at 20:28

2 Answers2

5
import sys, time
while True:
    for i in range( 4 ):
        sys.stdout.write( '\r' + ( '.' * i ) + '   ' )
        sys.stdout.flush()
        time.sleep( 0.5 )

That does the animation on the command line. There should be enough examples on threading in Python here.

edit:

Possible solution with threading; don't know if writing a real thread would be more efficient as I don't thread with python much.. from threading import Timer import sys, time

def animation ( i = 0 ):
    sys.stdout.write( '\r' + ( '.' * i ) + '   ' )
    sys.stdout.flush()
    Timer( 0.5, animation, ( 0 if i == 3 else i + 1, ) ).start()

animation()
print( 'started!' )

while True:
    pass
poke
  • 369,085
  • 72
  • 557
  • 602
  • Thanks, this is great, but the carriage return doesn't appear to be working. It jumps to the next line – Chris Laplante Mar 24 '11 at 20:42
  • Hmm. Jython might be the reason then; no idea about that though, I only use CPython :/ – poke Mar 24 '11 at 20:50
  • Switched to Python 2.7.1, but still not working. All well. I'm not going to get hung up on a progress indicator. Marking yours as correct though since I know it *should* work for me. – Chris Laplante Mar 24 '11 at 20:54
  • huh, I just switched to my Python2 installation (I usually use Python3) just to test it (running Python 2.7), and it did work there too :o Really strange.. – poke Mar 24 '11 at 20:57
1

I've implemented something similar in Python. Take a look at this and modify it as you wish to fit your needs:

class ProgressBar(object):

    def __init__(self, min_val=0, max_val=100, width=30, stdout=sys.stdout):
        self._progress_bar = '[]'   # holds the progress bar string
        self._old_progress_bar = '[]'

        self.min = min_val
        self.max = max_val
        self.span = max_val - min_val
        self.width = width
        self.current = 0            # holds current progress value
        self.stdout = stdout

        self.update(min_val)        # builds the progress bar string

    def increment(self, incr):
        self.update(self.current + incr)

    def update(self, val):
        """Rebuild the progress bar string with the given progress
        value as reference.

        """
        # cap the value at [min, max]
        if val < self.min: val = self.min
        if val > self.max: val = self.max
        self.current = val 

        # calculate percentage done
        diff = self.current - self.min
        done = int(round((float(diff) / float(self.span)) * 100.0))

        # calculate corresponding number of filled spaces
        full = self.width - 2 
        filled = int(round((done / 100.0) * full))

        # build the bar
        self._progress_bar = '[%s>%s] %d%%' % \ 
          ('=' * (filled - 1), ' ' * (full - filled), done)

    def draw(self, padding=0):
        """Draw the progress bar to current line in stdout.

        """
        if self._old_progress_bar != self._progress_bar:
            self._old_progress_bar = self._progress_bar
            self.stdout.write('\r%s%s ' % 
              (' ' * padding, self._progress_bar))
            self.stdout.flush()      # force stdout update

    def close(self):
        """Finish the progress bar. Append a newline and close
        stdout handle.

        """
        self.stdout.write('\n')
        self.stdout.flush()

    def __str__(self):
        return self._progress_bar

Sample usage:

def reporter(count, size, total):
    """The reporthook callback."""
    if self._progbar is None:
        self._progbar = ProgressBar(max_val=total)

    self._progbar.increment(size)
    self._progbar.draw(padding=3)

try:
    message = 'Begin downloading %s to %s' % (self.url, self.to)
    LOGGER.debug(message)
    print message
    filename, headers = urllib.urlretrieve(self.url, self.to, reporter)
    print 'Download finished.'
except:
    LOGGER.exception('Download interrupted: %s' % sys.exc_info()[0])
finally:
    self._progbar.close()
Santa
  • 11,381
  • 8
  • 51
  • 64