Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Diálogo de progreso muy pythónico

Este es un ejemplo que te muestra suficiente para usar progressbar:

progress = ProgressBar()
for i in progress(range(80)):

Sí, eso es to­do, te­nés una lin­da ba­rra de pro­gre­so AS­CII que cru­za la ter­mi­na­l, so­por­ta que la cam­bies de ta­ma­ño y se mue­ve mien­tras ite­rás de 0 a 79.

El módulo progressbar incluso tiene cosas mejores como ETA o velocidades de transferencia, y todo es así de fácil.

¿E­se có­di­go­... no es­tá bue­no? ¿Que­rés una ba­rra de pro­gre­so pa­ra ese lo­op? ¡Lo "en­vol­vé­s" y lis­to! Y por su­pues­to, co­mo yo pro­gra­mo con Py­Q­t, quie­ro que Py­Qt ten­ga al­go igual de bue­no.

Así se ve el re­sul­ta­do:


Es­to lo po­dés ha­cer con cual­quier toolki­t, y pro­ba­ble­men­te de­be­ría­s. Tie­ne un fea­tu­re ex­tra: po­dés in­te­rrum­pir la ite­ra­ció­n, y es­te es el (po­co) có­di­go:

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

def progress(data, *args):
    widget = QtGui.QProgressDialog(*args+(0,it.__length_hint__()))
    for v in it:
        if widget.wasCanceled():
            raise StopIteration

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

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


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?

Contents © 2000-2023 Roberto Alsina