3: LE list comprehension

Liste al volo

Nella pratica della programmazione accade spesso di dover creare una lista usando un ciclo for che calcola i suoi elementi. Ecco un semplice esempio in IDLE che crea una lista di quadrati:


>>> listaquad = []
>>> for i in range(1, 11):
	    listaquad.append(i ** 2)
>>> listaquad
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

(per far eseguire ad IDLE il ciclo for dovete premere due volte <INVIO>). Per casi come questo Python ha una sintassi molto più compatta e leggibile, quella delle list comprehensions. Infatti potete ottenere lo stesso risultato con una sola riga di codice, così:


>>> listaquad = [i ** 2 for i in range(1, 11)]
>>> listaquad
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

L'espressione tra parentesi quadre nella prima istruzione non è una lista, ma una list comprehension, cioè un tipo di espressione che crea una lista "al volo"; vediamo la sua sintassi:

Una list comprehension è racchiusa tra parentesi quadre; essa è formata da un'espressione, seguita da uno o più for ed (opzionalmente) da uno o più if.

Quando incontra una comprehension Python crea automaticamente una lista (che sarà il risultato della comprehension) e vi aggiunge tutti i valori dell'espressione al suo interno calcolati eseguendo il ciclo for. Quindi una comprehension non fa nulla di nuovo, ma permette di semplificare notevolmente il codice, ottenendo con una sintassi compatta ed intuitiva quello che richiederebbe parecchie istruzioni (i programmatori parlano di "syntactic sugar": qualcosa di non strettamente necessario ma comunque esteticamente bello ed utile). Vediamo ora qualche altro esempio:


>>> ["aaa" + c for c in "wxyz"]   
['aaaw', 'aaax', 'aaay', 'aaaz']

>>> import math                                       # per usare la sqrt()
>>> [round(math.sqrt(x), 4) for x in range(1, 10)]    # radici quadrate dei numeri da 1 a 9

[1.0, 1.4142, 1.7321, 2.0, 2.2361, 2.4495, 2.6458, 2.8284, 3.0]

>>> [(i, i ** 2, i ** 3) for i in (1, 5, 10)]         # tuple con numero, quadrato e cubo

[(1, 1, 1), (5, 25, 125), (10, 100, 1000)]

Notiamo che:

Le variabili contatore che creiamo all'interno delle parentesi quadre si comportano come le variabili locali delle funzioni: esse vengono create e distrutte all'interno della comprehension e non esistono al di fuori. Invece è possibile usare nella comprehension una variabile definita all'esterno di essa:


>>> c = 10        # definiamo una variabile
>>> i = 5         # un'altra
>>> [c * i * i for i in range(1, 10)]

[10, 40, 90, 160, 250, 360, 490, 640, 810]

>>> i             # i e' rimasta invariata

5

La variabile i usata all'interno della comprehension non ha modificato quella preesistente. Nella documentazione ufficiale si fa notare che questo fatto permette di risparmiare memoria ed aumentare la sicurezza (creando la lista con un ciclo for la i sarebbe stata invece modificata, magari inavvertitamente).

ESERCIZIO 3.1: Digitate queste righe di testo in IDLE e, prima di dare INVIO, cercate di immaginare il risultato di ogni list comprehension:

>>> lettere = "abcdef"
>>> [2 * c for c in lettere]
>>> [lettere[i:] for i in range(len(lettere))]
>>> [(i + 1, lettere[i]) for i in range(len(lettere))]
ESERCIZIO 3.2: Definite (se non lo avete già fatto) la variabile lettere come nell'esercizio precedente; scrivete poi delle list comprehensions che diano come risultato le seguenti liste:

['a_a', 'b_b', 'c_c', 'd_d', 'e_e', 'f_f']
['a', 'bb', 'ccc', 'dddd', 'eeeee', 'ffffff']
[('abcdef', 1), ('bcdef', 2), ('cdef', 3), ('def', 4), ('ef', 5), ('f', 6)]

CICLI ANNIDATI

Vediamo ora cosa succede se scriviamo più di un for: in questo caso essi si comportano come cicli annidati, da sinistra a destra. Quindi:


l = [(x, y) for x in (1, 2, 3) for y in (1, 2, 3)]

è l'equivalente di:


l = []
    for x in (1, 2, 3)
        for y in (1, 2, 3)
            l.append((x, y))

Ecco qualche esempio:


>>> [(x, y) for x in (1, 2, 3) for y in (1, 2, 3)]

[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]

>>> [(x, y) for x in (1, 2, 3) for y in range(1, x+1)]

[(1, 1), (2, 1), (2, 2), (3, 1), (3, 2), (3, 3)]

Notate che, nel secondo caso, abbiamo usato nel secondo for il contatore x del primo, cosa possibile perchè esso è già stato definito (appunto nel primo for).

ESERCIZIO 3.3 Scrivete delle list comprehensions che producano questi risultati

[(1, a), (1, b), (2, a), (2, b)]
['ax', 'ay', 'bx', 'by', 'cx', 'cy', 'dx', 'dy']
[1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9 , 12]

CICLI CON if

Infine possiamo inserire dopo i for delle istruzioni if, tipicamente per escludere qualche caso nell'esecuzione dei cicli.


>>> [i for i in range(1, 11) if i not in (3, 6)]

[1, 2, 4, 5, 7, 8, 9, 10]

>>> squadre = ["Milan", "Inter", "Juventus", "Roma"]
>>> partite = [x + " - " + y for x in squadre for y in squadre if x != y]
>>> partite
['Milan - Inter', 'Milan - Juventus', 'Milan - Roma', 'Inter - Milan', 'Inter - Juventus',
'Inter - Roma', 'Juventus - Milan', 'Juventus - Inter', 'Juventus - Roma', 'Roma - Milan',
'Roma - Inter', 'Roma - Juventus']

Python richiede che il primo if segua almeno un for (tenendo sempre presente che le variabili che vi compaiono devono già essere state definite), in ogni caso per non sbagliare è prassi comune mettere tutti gli if alla fine, dopo i for.

FILTRI

Le list comprehension con gli if vengono spesso usate come filtri, cioè per selezionare da una lista o dizionario una sottosequenza che contiene solo i dati che ci interessano (facendo quindi un lavoro simile all'estrazione di dati da un database). Per capire meglio riprendiamo il nostro esempio dell'alunno di una scuola memorizzato mediante un dizionario, fatto qui nella lezione precedente. Copiate in IDLE questa (lunghissima) riga di codice:


lista_alunni = [
	{"nome":"Mario", "cognome":"Rossi", "classe":"1A",
	 "voti":{"Italiano":6, "Matematica":5, "Inglese":7 }},
	{"nome":"Lucia", "cognome":"Bianchi", "classe":"1B",
	 "voti":{"Italiano":7, "Matematica":8, "Inglese":8 }},
	{"nome":"Luigi", "cognome":"Neri", "classe":"2A",
	 "voti":{"Italiano":5, "Matematica":4, "Inglese":6 }},
	{"nome":"Laura", "cognome":"Verdi", "classe":"1A",
	 "voti":{"Italiano":7, "Matematica":8, "Inglese":7 }},
	{"nome":"LPietro", "cognome":"Gialli", "classe":"1A",
	 "voti":{"Italiano":7, "Matematica":8, "Inglese":5 }}
	 ]

Ho semplificato un po' l'esempio per non allungare troppo: potete comunque facilmente capire che la variabile lista_alunni contiene i dati di 5 studenti: nome, cognome, classe, e i voti in Italiano, Matematica e Inglese (ovviamente in una vera scuola la lista potrebbe contenere centinaia di studenti).

Ora voglio sapere quali di questi alunni hanno avuto un'insufficienza in matematica: posso farlo in un'unica istruzione usando una list comprehension:


>>> [(x["nome"], x["cognome"]) for x in lista_alunni if
		     x["voti"]["Matematica"] < 6 ]
	      
[('Mario', 'Rossi'), ('Luigi', 'Neri')]

La mia comprehension ha creato una lista di tuple che contengono il nome e cognome dell'alunno, selezionando tutti gli alunni con voto in matematica inferiore a 6.

ESERCIZIO 3.4 Sempre facendo riferimento alla lista lista_alunni modificate l'istruzione if in modo da ottenere: Usando poi degli and possiamo ottenere condizioni ancora più sofisticate:

Fine della lezione