Skip to main content

Ralsina.Me — Roberto Alsina's website

Very pythonic progress dialogs.

Sometimes, you see a piece of code and it just feels right. Here's an example I found when doing my "Import Antigravity" session for PyDay Buenos Aires: the progressbar module.

Here's an example that will teach you enough to use progressbar effectively:

progress = ProgressBar()
for i in progress(range(80)):
    time.sleep(0.01)

Yes, that's it, you will get a nice ASCII progress bar that goes across the ter­mi­nal, sup­ports re­siz­ing and moves as you it­er­ate from 0 to 79.

The progressbar module even lets you do fancier things like ETA or fie transfer speeds, all just as nicely.

Is­n't that code just right? You want a progress bar for that loop? Wrap it and you have one! And of course since I am a PyQt pro­gram­mer, how could I make PyQt have some­thing as right as that?

Here'show the out­put looks like:

progress

You can do this with ev­ery toolk­it, and you prob­a­bly should!. It has one ex­tra fea­ture: you can in­ter­rupt the it­er­a­tion. Here's the (short) code:

# -*- coding: utf-8 -*-
import sys, time
from PyQt4 import QtCore, QtGui

def progress(data, *args):
    it=iter(data)
    widget = QtGui.QProgressDialog(*args+(0,it.__length_hint__()))
    c=0
    for v in it:
        QtCore.QCoreApplication.instance().processEvents()
        if widget.wasCanceled():
            raise StopIteration
        c+=1
        widget.setValue(c)
        yield(v)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)

    # Do something slow
    for x in progress(xrange(50),"Show Progress", "Stop the madness!"):
        time.sleep(.2)

Have fun!

Pierpaolo Da Fieno / 2010-09-14 22:52:

Really nice one, but I would not use __length_hint__ because, as far as I know, is not documented and could disappear in any future version. Why not len(data)?
What about killing the c variable and writing something like: widget.setValue(widget.value()+1).
I did a quick profiling and checked that cpu time is almost identical (actually a slight advanced on this last version, though I can't say why...)
Which one is more pythonic?

Best Regards

Roberto Alsina / 2010-09-15 03:13:

Indeed len(data) is saner!

I just got confused becaue iter(data) had no length anymore!

I prefer the explicit counter because it looks more clear to me, but it's not a big difference :-)

Brandon Rhodes / 2010-09-15 03:08:

Wow! It took me two readings of the code to realize it was a sequence wrapper that does not modify the sequence — I have never seen one of those before! Very elegant.

Roberto Alsina / 2010-09-15 03:14:

Thanks! Of course it was not my idea :-)

Arnar Birgisson / 2010-09-15 21:25:

Will it convert an iterator into a list to measure its length? E.g. in your QT example where you use xrange.

Roberto Alsina / 2010-09-15 23:04:

I don't think so. But how could I test it?

Arnar Birgisson / 2010-09-16 00:14:

The way you use __length_hint__ now doesn't convert it to a list. If you change that to len(list(it)) then of course it will.

You can test it by creating a large text file (say 100mb) and using file.xreadlines to iterate over them. If the memory usage of the program stays low - it is not creating a list. If you are then it will need to allocate 100mb of memory.

You can also test it more directly by giving it an iterator with side effects.

def aniterator():
for n in range(5):
print("yielding", n)
yield n

for x in progress(aniterator()):
print("processing", x)

If you get all yields before processing, it is creating a list. If it is not, you will get alternating "yielding" and "processing" messages.

wendell / 2010-09-15 21:46:

I think len(data) won't work with xrange, for example, which is probably why he is using __length__hint__. Perhaps he should default back to len(list(iter)).

Also... on hitting cancel, it seems you might want to raise an error other than StopIteration, because with StopIteration, its hard to tell the difference outside between simply hitting the end of the loop and 'cancel' being called...

Roberto Alsina / 2010-09-15 23:03:

Actually len(xrange(100)) does work. I am not sure why I am using __length__hint__ I wrote this in a hurry :-)

Yes, what exception to throw is a matter of taste.

Cwillu / 2010-09-16 10:05:

Why throw an exception, rather than just returning?