4.12. Cicli for

Come molti altri linguaggi, Python ha i cicli for. L'unica ragione per cui non si sono visti finora è perché Python è valido per così tante cose che spesso non se ne sente tanto il bisogno.

Molti altri linguaggi non hanno tipi di liste così potenti come quelle di Python, cosicché si finisce per fare un sacco di lavoro in più a mano, dovendo specificare l'inizio, la fine e l'incremento necessario per definire un intervallo di numeri o di caratteri o altre entità su cui è possibile iterare. In Python invece un ciclo for itera semplicemente su una lista, nello stesso modo in cui lavorano le list comprehensions.

Esempio 4.27. Introduzione al ciclo 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 sintassi per un ciclo for è simile a quella delle list comprehensions. La variabile li è una lista e la variabile s assume a turno il valore di ogni elemento della lista, a partire dal primo elemento.
2 Come in una istruzione if o in ogni altro blocco indentato, un ciclo for può avere un qualsiasi numero di linee al uso interno.
3 Questa è la ragione per cui non si è ancora visto un ciclo for: non ne abbiamo ancora avuto il bisogno. È incredibile quanto spesso si usi il ciclo for in altri linguaggi quando tutto quello che si vuole realmente è una join o una list comprehension.

Esempio 4.28. Semplici contatori

>>> 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 Eseguire un “normale” (secondo gli standard Visual Basic) ciclo for su di un contatore è anche semplice. Come si vede nell'Esempio 2.28, “Assegnare valori consecutivi”, la funzione range produce una lista di interi, attraverso la quale iteriamo. Lo so, sembra un po' strano, ma è occasionalmente (e sottolineo occasionalmente) utile avere un ciclo con contatore.
2 Non fatelo mai. Questo è il modo di pensare secondo lo stile di Visual Basic. Occorre staccarsene. Semplicemente, iterate attraverso una lista, come mostrato nell'esempio precedente.

Esempio 4.29. Iterare sugli elementi di un dizionario

>>> 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 è il dizionario delle variabili d'ambiente definite dal sistema su cui gira Python. In Windows, queste corrispondono alle variabili utente e di sistema accessibili da MS-DOS. In UNIX, sono le variabili esportate dallo script di inizializzazione della shell usata. In Mac OS, non esiste il concetto di variabile d'ambiente, per cui questo dizionario è vuoto.
2 os.environ.items() restituisce una lista di tuple: [(chiave1, valore1), (chiave2, valore2), ...]. Il ciclo for itera su questa lista. Al primo giro, si assegna chiave1 alla variabile k e valore1 alla variabile v, cosicché k = USERPROFILE e v = C:\Documents and Settings\mpilgrim. Al secondo giro, la variabile k prende il valore della seconda chiave, OS e la variabile v prende il valore corrispondente, Windows_NT.
3 Con assegnamenti multipli e list comprehensions, è possibile rimpiazzare un intero ciclo for con una singola istruzione. Se farlo o no in codice non didattico dipende dal personale stile di codifica di ognuno; Io preferisco questo modo perché rende evidente che quello che si vuole fare è mappare un dizionario in una lista, quindi unire tutti gli elementi di una lista in una singola stringa. Altri programmatori preferiscono scriverlo come un ciclo for. Si noti che il risultato è lo stesso in entrambi i casi, sebbene questa versione sia leggermente più veloce perché vi è una sola istruzione print invece di molte.

Esempio 4.30. Ciclo for in 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 è un attributo di classe che definisce i tag che si trovano in un file MP3. Questi tag sono memorizzati in campi a lunghezza fissa; degli ultimi 128 byte letti, i byte da 3 a 32 sono sempre il titolo della canzone, i byte 33-62 sono sempre il nome dell'artista e così via. Si noti che tagDataMap è un dizionario che contiene tuple ed ogni tupla contiene due interi ed un riferimento a funzione.
2 Questo sembra complicato ma non lo è. La struttura delle variabili usate nell'istruzione for, corrisponde alla struttura degli elementi della lista, restituita dal metodo items. Occorre ricordarsi che il metodo items restituisce una lista di tuple nella forma (chiave, valore). Il primo elemento di questa lista è ("title", (3, 33, <function stripnulls>)), quindi al primo ciclo di iterazione, la variabile tag prende il valore "title", la variabile start prende il valore 3, la variabile end prende il valore 33 e la variabile parseFunc prende il valore stripnulls.
3 Una volta estratti tutti i parametri per un singolo MP3, salvare i valori dei tag estratti è facile. Prima si separano dalla lista dei byte contenenti i tagdata, i byte da start ad end, per ottenere i byte corrispondenti ad un singolo tag; poi si chiama la funzione invocata da parseFunc, per elaborare i byte estratti ed infine si assegna il valore risultante della chiave tag al pseudo-dizionario dell'oggetto self. Una volta iterato su tutti gli elementi di tagDataMap, l'oggetto self contiene i valori di tutti i tag e si sa questo cosa vuol dire. (ndt: i tag risultano accessibili come normali attributi dell'oggetto).