Usare la notazione di comprensione delle liste in Python

Attività commerciale

In Python, è semplice usare la notazione di comprensione delle liste quando si genera una nuova lista.(List comprehensions)

In questo articolo, parleremo prima di tutto di quanto segue

  • Notazione di base per la comprensione delle liste
  • Notazione di comprensione della lista con ramificazione condizionale tramite if
  • Combinazione con operatori ternari (elaborazione tipo if else)
  • zip(),enumerate()Combinazione con questi
  • notazione di inclusione della lista annidata

In seguito, spiegheremo l'insieme della notazione di comprensione della lista con codice di esempio.

  • notazione di inclusione degli insiemi(Set comprehensions)
  • notazione di inclusione del dizionario(Dict comprehensions)
  • tipo di generatore(Generator expressions)

Notazione di base per la comprensione delle liste

La notazione di comprensione della lista è scritta come segue.

[Expression for Any Variable Name in Iterable Object]

Prende ogni elemento di un oggetto iterabile come una lista, una tupla o un intervallo da un nome variabile arbitrario e lo valuta con un'espressione. Viene restituita una nuova lista con il risultato della valutazione come elemento.

Viene dato un esempio insieme a una dichiarazione for equivalente.

squares = [i**2 for i in range(5)]
print(squares)
# [0, 1, 4, 9, 16]
squares = []
for i in range(5):
    squares.append(i**2)

print(squares)
# [0, 1, 4, 9, 16]

Lo stesso processo può essere fatto con map(), ma la notazione di comprensione della lista è preferita per la sua semplicità e chiarezza.

Notazione di comprensione della lista con ramificazione condizionale tramite if

La ramificazione condizionale con if è anche possibile. Scrivete l'if nel postfisso come segue.

[Expression for Any Variable Name in Iterable Object if Conditional Expression]

Solo gli elementi dell'oggetto iterabile la cui espressione condizionale è vera sono valutati dall'espressione, e viene restituita una nuova lista i cui elementi sono il risultato.

Potete usare qualsiasi nome di variabile nell'espressione condizionale.

Viene dato un esempio insieme a una dichiarazione for equivalente.

odds = [i for i in range(10) if i % 2 == 1]
print(odds)
# [1, 3, 5, 7, 9]
odds = []
for i in range(10):
    if i % 2 == 1:
        odds.append(i)

print(odds)
# [1, 3, 5, 7, 9]

Lo stesso processo può essere fatto con filter(), ma la notazione di comprensione della lista è preferita per la sua semplicità e chiarezza.

Combinazione con operatori ternari (elaborazione tipo if else)

Nell'esempio precedente, solo gli elementi che soddisfano i criteri vengono processati, e quelli che non soddisfano i criteri vengono esclusi dalla nuova lista.

Se volete cambiare il processo a seconda della condizione, o se volete processare diversamente gli elementi che non soddisfano la condizione, come in if else, usate l'operatore ternario.

In Python, l'operatore ternario può essere scritto come segue

Value When True if Conditional Expression else Value When False

Questo è usato nella parte di espressione della notazione di comprensione della lista come mostrato qui sotto.

[Value When True if Conditional Expression else Value When False for Any Variable Name in Iterable Object]

Viene dato un esempio insieme a una dichiarazione for equivalente.

odd_even = ['odd' if i % 2 == 1 else 'even' for i in range(10)]
print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
odd_even = []
for i in range(10):
    if i % 2 == 1:
        odd_even.append('odd')
    else:
        odd_even.append('even')

print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']

È anche possibile scrivere espressioni usando nomi di variabili arbitrari per i valori vero e falso.

Se la condizione è soddisfatta, viene fatta qualche elaborazione, altrimenti il valore dell'oggetto iterabile originale viene lasciato invariato.

odd10 = [i * 10 if i % 2 == 1 else i for i in range(10)]
print(odd10)
# [0, 10, 2, 30, 4, 50, 6, 70, 8, 90]

Combinazione con zip() e enumerate()

Le funzioni utili che sono spesso usate nell'istruzione for includono zip(), che combina più iterabili, e enumerate(), che restituisce un valore insieme al suo indice.

Naturalmente, è possibile usare zip() ed enumerate() con la notazione di comprensione della lista. Non è una sintassi speciale, e non è difficile se si considera la corrispondenza con l'istruzione for.

Esempio di zip().

l_str1 = ['a', 'b', 'c']
l_str2 = ['x', 'y', 'z']

l_zip = [(s1, s2) for s1, s2 in zip(l_str1, l_str2)]
print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]
l_zip = []
for s1, s2 in zip(l_str1, l_str2):
    l_zip.append((s1, s2))

print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]

Esempio di enumerare().

l_enu = [(i, s) for i, s in enumerate(l_str1)]
print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]
l_enu = []
for i, s in enumerate(l_str1):
    l_enu.append((i, s))

print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]

L'idea è la stessa di prima quando si usa if.

l_zip_if = [(s1, s2) for s1, s2 in zip(l_str1, l_str2) if s1 != 'b']
print(l_zip_if)
# [('a', 'x'), ('c', 'z')]

Ogni elemento può anche essere usato per calcolare un nuovo elemento.

l_int1 = [1, 2, 3]
l_int2 = [10, 20, 30]

l_sub = [i2 - i1 for i1, i2 in zip(l_int1, l_int2)]
print(l_sub)
# [9, 18, 27]

notazione di inclusione della lista annidata

Come l'annidamento dei cicli for, anche la notazione di comprensione della lista può essere annidata.

[Expression for Variable Name 1 in Iterable Object 1
    for Variable Name 2 in Iterable Object 2
        for Variable Name 3 in Iterable Object 3 ... ]

Per comodità, sono state aggiunte interruzioni di riga e rientri, ma non sono necessari per la grammatica; possono essere continuati su una singola riga.

Viene dato un esempio insieme a una dichiarazione for equivalente.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

flat = [x for row in matrix for x in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat = []
for row in matrix:
    for x in row:
        flat.append(x)

print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

È anche possibile utilizzare più variabili.

cells = [(row, col) for row in range(3) for col in range(2)]
print(cells)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

Si può anche fare una ramificazione condizionale.

cells = [(row, col) for row in range(3)
         for col in range(2) if col == row]
print(cells)
# [(0, 0), (1, 1)]

È anche possibile diramare condizionatamente per ogni oggetto iterabile.

cells = [(row, col) for row in range(3) if row % 2 == 0
         for col in range(2) if col % 2 == 0]
print(cells)
# [(0, 0), (2, 0)]

notazione di inclusione degli insiemi(Set comprehensions)

Cambiando le parentesi quadre [] nella notazione di comprensione della lista con parentesi graffe {} si crea un set (oggetto di tipo set).

{Expression for Any Variable Name in Iterable Object}
s = {i**2 for i in range(5)}

print(s)
# {0, 1, 4, 9, 16}

notazione di inclusione del dizionario(Dict comprehensions)

I dizionari (oggetti di tipo dict) possono anche essere generati con la notazione di comprensione.

{}, e specificare la chiave e il valore nella parte dell'espressione come chiave: valore.

{Key: Value for Any Variable Name in Iterable Object}

Qualsiasi espressione può essere specificata per chiave e valore.

l = ['Alice', 'Bob', 'Charlie']

d = {s: len(s) for s in l}
print(d)
# {'Alice': 5, 'Bob': 3, 'Charlie': 7}

Per creare un nuovo dizionario da una lista di chiavi e valori, usate la funzione zip().

keys = ['k1', 'k2', 'k3']
values = [1, 2, 3]

d = {k: v for k, v in zip(keys, values)}
print(d)
# {'k1': 1, 'k2': 2, 'k3': 3}

tipo di generatore(Generator expressions)

Se le parentesi quadre [] nella notazione di comprensione della lista sono usate come parentesi tonde (), viene restituito un generatore invece di una tupla. Questo è chiamato generatore di espressioni.

Esempio di notazione di comprensione della lista.

l = [i**2 for i in range(5)]

print(l)
# [0, 1, 4, 9, 16]

print(type(l))
# <class 'list'>

Esempio di espressione di un generatore. Se stampate() il generatore così com'è, non stamperà il suo contenuto, ma se lo eseguite con un'istruzione for, potete ottenere il contenuto.

g = (i**2 for i in range(5))

print(g)
# <generator object <genexpr> at 0x10af944f8>

print(type(g))
# <class 'generator'>

for i in g:
    print(i)
# 0
# 1
# 4
# 9
# 16

Le espressioni del generatore permettono anche la ramificazione condizionale e l'annidamento usando la notazione if e la notazione di comprensione della lista.

g_cells = ((row, col) for row in range(0, 3)
           for col in range(0, 2) if col == row)

print(type(g_cells))
# <class 'generator'>

for i in g_cells:
    print(i)
# (0, 0)
# (1, 1)

Per esempio, se una lista con un gran numero di elementi viene generata usando la notazione di comprensione della lista e poi si fa un ciclo con un'istruzione for, la lista contenente tutti gli elementi sarà generata all'inizio se si usa la notazione di comprensione della lista. D'altra parte, se si usa un'espressione generatrice, ogni volta che il ciclo viene ripetuto, gli elementi vengono generati uno per uno, riducendo così la quantità di memoria utilizzata.

Se l'espressione del generatore è l'unico argomento della funzione, le parentesi tonde () possono essere omesse.

print(sum([i**2 for i in range(5)]))
# 30

print(sum((i**2 for i in range(5))))
# 30

print(sum(i**2 for i in range(5)))
# 30

Per quanto riguarda la velocità di elaborazione, la notazione di comprensione della lista è spesso più veloce della notazione del generatore quando tutti gli elementi sono elaborati.

Tuttavia, quando si giudica con all() o any(), per esempio, il risultato è determinato quando è presente false o true, quindi usare espressioni generatrici può essere più veloce che usare la notazione di comprensione della lista.

Non esiste una notazione di comprensione delle tuple, ma se usate un'espressione generatrice come argomento di tuple(), potete generare una tupla nella notazione di comprensione.

t = tuple(i**2 for i in range(5))

print(t)
# (0, 1, 4, 9, 16)

print(type(t))
# <class 'tuple'>