6.3. Iteración con bucles for

Como la mayoría de los otros lenguajes, Python cuenta con bucles for. La única razón por la que no los ha visto antes es que Python es bueno en tantas otras cosas que no los ha necesitado hasta ahora con tanta frecuencia.

La mayoría de los otros lenguajes no tiene el poderoso tipo de lista como Python, de manera que se acaba haciendo mucho trabajo manual, especificando un inicio, fin y paso que define un rango de enteros o caracteres y otras entidades iterables. Pero en Python, un bucle for simplemente itera sobre una lista, de la misma manera que funcionan las listas por comprensión.

Ejemplo 6.8. Presentación del bucle for

>>> li = ['a', 'b', 'e']
>>> for s in li:         1
...     print s          2
a
b
e
>>> print "\n".join(li)  3
a
b
e
1 La sintaxis del bucle for es similar a la de las listas por comprensión. li es una lista, y s tomará el valor de cada elemento por turnos, comenzando por el primero.
2 Como una sentencia if o cualquier otro bloque sangrado, un bucle for puede constar de cualquier número de líneas.
3 Ésta es la razón por la que aún no ha visto aún el bucle for: todavía no lo habíamos necesitado. Es impresionante lo a menudo que se usan los bucles for en otros lenguajes cuando todo lo que desea realmente es un join o una lista por comprensión.

Hacer un bucle contador “normal” (según estándares de Visual Basic) también es sencillo.

Ejemplo 6.9. Contadores simples

>>> for i in range(5):             1
...     print i
0
1
2
3
4
>>> li = ['a', 'b', 'c', 'd', 'e']
>>> for i in range(len(li)):       2
...     print li[i]
a
b
c
d
e
1 Como pudo ver en Ejemplo 3.20, “Asignación de valores consecutivos”, range produce una lista de enteros, sobre la que puede iterar. Sé que parece un poco extraño, pero ocasionalmente (y subrayo ese ocasionalmente) es útil tener un bucle contador.
2 Nunca haga esto. Esto es pensar estilo Visual Basic. Despréndase de eso. Simplemente, itere sobre la lista, como se mostró en el ejemplo anterior.

Los bucles for no son sólo simples contadores. Puede interar sobre todo tipo de cosas. Aquí tiene un ejemplo de uso de un bucle for para iterar sobre un diccionario.

Ejemplo 6.10. Iteración sobre un diccionario

>>> import os
>>> for k, v in os.environ.items():      1 2
...     print "%s=%s" % (k, v)
USERPROFILE=C:\Documents and Settings\mpilgrim
OS=Windows_NT
COMPUTERNAME=MPILGRIM
USERNAME=mpilgrim

[...snip...]
>>> print "\n".join(["%s=%s" % (k, v)
...     for k, v in os.environ.items()]) 3
USERPROFILE=C:\Documents and Settings\mpilgrim
OS=Windows_NT
COMPUTERNAME=MPILGRIM
USERNAME=mpilgrim

[...snip...]
1 os.environ es un diccionario de las variables de entorno definidas en su sistema. En Windows, son su usuario y las variables accesibles desde MS-DOS. En UNIX, son las variables exportadas por los scripts de inicio de su intérprete de órdenes. En Mac OS, no hay concepto de variables de entorno, de manera que el diccionario está vacío.
2 os.environ.items() devuelve una lista de tuplas: [(clave1, valor1), (clave2, valor2), ...]. El bucle for itera sobre esta lista. En la primera iteración, asigna clave1 a k y valor1 a v, de manera que k = USERPROFILE y v = C:\Documents and Settings\mpilgrim. En la segunda iteración, k obtiene la segunda clave, OS, y v el valor correspondiente, Windows_NT.
3 Con la asignación de múltiples variables y las listas por comprensión, puede reemplazar todo el bucle for con una sola sentencia. Hacer o no esto en código real es una cuestión de estilo personal al programar. Me gusta porque deja claro que lo que hago es relacionar un diccionario en una lista, y luego unir la lista en una única cadena. Otros programadores prefieren escribir esto como un bucle for. La salida es la misma en cualquier caso, aunque esta versión es ligeramente más rápidao, porque sólo hay una sentencia print en lugar de muchas.

Ahora podemos mirar el bucle for de MP3FileInfo, del programa de ejemplo fileinfo.py que presentamos en Capítulo 5.

Ejemplo 6.11. Bucle for en MP3FileInfo

    tagDataMap = {"title"   : (  3,  33, stripnulls),
                  "artist"  : ( 33,  63, stripnulls),
                  "album"   : ( 63,  93, stripnulls),
                  "year"    : ( 93,  97, stripnulls),
                  "comment" : ( 97, 126, stripnulls),
                  "genre"   : (127, 128, ord)}                               1
    .
    .
    .
            if tagdata[:3] == "TAG":
                for tag, (start, end, parseFunc) in self.tagDataMap.items(): 2
                    self[tag] = parseFunc(tagdata[start:end])                3
1 tagDataMap es un atributo de clase que define las etiquetas que estamos buscando en un fichero MP3. Las etiquetas se almacenan en campos de longitud fija. Una vez haya leído los últimos 128 bytes del fichero, los bytes 3 al 32 siempre corresponden al título de la canción, del 33 al 62 es siempre el nombre del artista, del 63 al 92 tenemos el nombre del álbum, y así en adelante. Observe que tagDataMap es un diccionario de tuplas, y cada tupla contiene dos enteros y una referencia a una función.
2 Parece complicado, pero no lo es. La estructura de las variables del for se corresponden a la estructura de los elementos de la lista devuelta por items. Recuerde que items devuelve una lista de tuplas de la forma (clave, valor). El primer elemento de esa lista es ("title", (3, 33, <function stripnulls>)), de manera que en la primera iteración del bucle, tag contiene "title", start contiene 3, end contiene 33, y parseFunc tendrá asignada la función stripnulls.
3 Ahora que hemos extraído todos los parámetros de una etiqueta de MP3, guardar sus datos es sencillo. Haremos un slice de tagdata desde start hasta end para obtener el dato de esa etiqueta, llamaremos a parseFunc para postprocesar el dato, y lo asignaremos como valor de la clave tag en el pseudo-diccionario self. Tras iterar sobre todos los elementos de tagDataMap, self tiene los valores de todas las etiquetas, y ya sabemos qué aspecto tiene eso.