Richard Jones' Log: Just some code - a console progress display class

Fri, 10 Dec 2004

Getting back into the swing of posting to this log... here's my variant on the usual progress display. Percentage with an ETA if it appears to be a long-running task. Examples at the end of the code. And hey, it's my first iterator (using __iter__ and not the old-school __getitem__, that is ;)

import sys,time

class Progress:
    '''Progress display for console applications.

    See __main__ block at end of file for sample usage.
    '''
    def __init__(self, info, sequence):
        self.info = info
        self.sequence = iter(sequence)
        self.total = len(sequence)
        self.start = self.now = time.time()
        self.num = 0
        self.stepsize = self.total / 100 or 1
        self.steptimes = []
        self.display()

    def __iter__(self): return self

    def next(self):
        self.num += 1

        if self.num > self.total:
            print self.info, 'done', ' '*(75-len(self.info)-6)
            sys.stdout.flush()
            return self.sequence.next()

        if self.num % self.stepsize:
            return self.sequence.next()

        self.display()
        return self.sequence.next()

    def display(self):
        # figure how long we've spent - guess how long to go
        now = time.time()
        steptime = now - self.now
        self.steptimes.insert(0, steptime)
        if len(self.steptimes) > 5:
            self.steptimes.pop()
        steptime = sum(self.steptimes) / len(self.steptimes)
        self.now = now
        eta = steptime * ((self.total - self.num)/self.stepsize)

        # tell it like it is (or might be)
        if now - self.start > 3:
            M = eta / 60
            H = M / 60
            M = M % 60
            S = eta % 60
            s = '%s %2d%% (ETA %02d:%02d:%02d)'%(self.info,
                self.num * 100. / self.total, H, M, S)
        else:
            s = '%s %2d%%'%(self.info, self.num * 100. / self.total)
        sys.stdout.write(s + ' '*(75-len(s)) + '\r')
        sys.stdout.flush()

if __name__ == '__main__':
    for i in Progress('Testing 3...', range(3)):
        time.sleep(5)
    for i in Progress('Testing 1645...', range(1645)):
        time.sleep(.01)

It looks like there's a couple of entries in the cookbook that produce progress display. While this one has additional features, I don't think it's worth submitting.