Python Language
Elenco delle comprensioni
Ricerca…
introduzione
[i ** 2 for i in range(1,11)]
Il dummy i
di un range
elenchi esistente viene utilizzato per creare un nuovo modello di elemento. È usato dove sarebbe necessario un ciclo for in lingue meno espressive.
Sintassi
- [i for i in range (10)] # comprensione di base dell'elenco
- [i for i in xrange (10)] # comprensione di base dell'elenco con l'oggetto generatore in python 2.x.
- [i for i in range (20) if i% 2 == 0] # con filtro
- [x + y per x in [1, 2, 3] per y in [3, 4, 5]] # cicli annidati
- [i if i> 6 else 0 for i in range (10)] # espressione ternaria
- [i if i> 4 else 0 per i in range (20) if i% 2 == 0] # con filtro e espressione ternaria
- [[x + y per x in [1, 2, 3]] per y in [3, 4, 5]] # nidificazione di elenchi nidificati
Osservazioni
La comprensione delle liste è stata delineata in PEP 202 e introdotta in Python 2.0.
Comprensioni di liste condizionali
Data una comprensione lista è possibile aggiungere una o più if
le condizioni per filtrare i valori.
[<expression> for <element> in <iterable> if <condition>]
Per ogni <element>
in <iterable>
; se <condition>
restituisce True
, aggiungere <expression>
(di solito una funzione di <element>
) all'elenco restituito.
Ad esempio, questo può essere usato per estrarre solo numeri pari da una sequenza di numeri interi:
[x for x in range(10) if x % 2 == 0]
# Out: [0, 2, 4, 6, 8]
Il codice sopra è equivalente a:
even_numbers = []
for x in range(10):
if x % 2 == 0:
even_numbers.append(x)
print(even_numbers)
# Out: [0, 2, 4, 6, 8]
Inoltre, una comprensione dell'elenco condizionale della forma [e for x in y if c]
(dove e
c
sono espressioni in termini di x
) è equivalente alla list(filter(lambda x: c, map(lambda x: e, y)))
.
Nonostante fornisca lo stesso risultato, presta attenzione al fatto che l'esempio precedente è quasi 2x più veloce di quest'ultimo. Per coloro che sono curiosi, questa è una bella spiegazione del motivo per cui.
Si noti che questo è molto diverso dall'espressione condizionale ... if ... else ...
(a volte nota come espressione ternaria ) che è possibile utilizzare per la parte <expression>
della list comprehension. Considera il seguente esempio:
[x if x % 2 == 0 else None for x in range(10)]
# Out: [0, None, 2, None, 4, None, 6, None, 8, None]
Qui l'espressione condizionale non è un filtro, ma piuttosto un operatore che determina il valore da utilizzare per gli elementi dell'elenco:
<value-if-condition-is-true> if <condition> else <value-if-condition-is-false>
Questo diventa più ovvio se lo si combina con altri operatori:
[2 * (x if x % 2 == 0 else -1) + 1 for x in range(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Se si utilizza Python 2.7, xrange
può essere migliore range
per diversi motivi, come descritto nella documentazione di xrange
.
[2 * (x if x % 2 == 0 else -1) + 1 for x in xrange(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Il codice sopra è equivalente a:
numbers = []
for x in range(10):
if x % 2 == 0:
temp = x
else:
temp = -1
numbers.append(2 * temp + 1)
print(numbers)
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Si può combinare espressioni ternari e if
le condizioni. L'operatore ternario lavora sul risultato filtrato:
[x if x > 2 else '*' for x in range(10) if x % 2 == 0]
# Out: ['*', '*', 4, 6, 8]
Lo stesso non poteva essere raggiunto solo dall'operatore ternario:
[x if (x > 2 and x % 2 == 0) else '*' for x in range(10)]
# Out:['*', '*', '*', '*', 4, '*', 6, '*', 8, '*']
Vedi anche: Filtri , che spesso forniscono un'alternativa sufficiente alle comprensioni delle liste condizionali.
Elenco delle conclusioni con cicli annidati
Le comprensioni della lista possono usare annidati for
cicli. È possibile codificare qualsiasi numero di cicli for innestati all'interno di una lista di comprensione, e ciascuno for
ciclo può avere un optional associato if
prova. Nel fare ciò, l'ordine del for
costrutti è lo stesso ordine come quando si scrive una serie di nidificato for
dichiarazioni. La struttura generale delle list comprehensions si presenta così:
[ expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]...
for targetN in iterableN [if conditionN] ]
Ad esempio, il seguente codice che appiattisce un elenco di elenchi utilizzando più istruzioni for
:
data = [[1, 2], [3, 4], [5, 6]]
output = []
for each_list in data:
for element in each_list:
output.append(element)
print(output)
# Out: [1, 2, 3, 4, 5, 6]
può essere scritto in modo equivalente come una lista di comprensione con multipli for
costrutti:
data = [[1, 2], [3, 4], [5, 6]]
output = [element for each_list in data for element in each_list]
print(output)
# Out: [1, 2, 3, 4, 5, 6]
Sia nella forma espansa che nella comprensione della lista, il ciclo esterno (prima per la dichiarazione) viene prima di tutto.
Oltre ad essere più compatto, la comprensione annidata è anche significativamente più veloce.
In [1]: data = [[1,2],[3,4],[5,6]]
In [2]: def f():
...: output=[]
...: for each_list in data:
...: for element in each_list:
...: output.append(element)
...: return output
In [3]: timeit f()
1000000 loops, best of 3: 1.37 µs per loop
In [4]: timeit [inner for outer in data for inner in outer]
1000000 loops, best of 3: 632 ns per loop
Il sovraccarico per la chiamata di funzione sopra è di circa 140ns .
In linea if
s sono nidificati in modo simile e possono verificarsi in qualsiasi posizione dopo il primo for
:
data = [[1], [2, 3], [4, 5]]
output = [element for each_list in data
if len(each_list) == 2
for element in each_list
if element != 5]
print(output)
# Out: [2, 3, 4]
Per motivi di leggibilità, tuttavia, dovresti considerare l'utilizzo di for-loops tradizionali. Ciò è particolarmente vero quando la nidificazione è profonda più di 2 livelli e / o la logica della comprensione è troppo complessa. la comprensione di più elenchi di cicli annidati potrebbe essere soggetta a errori o fornire risultati imprevisti.
Refactoring filter e map to list comprehensions
Le funzioni di filter
o map
dovrebbero essere spesso sostituite dalla comprensione delle liste . Guido Van Rossum lo descrive bene in una lettera aperta nel 2005 :
filter(P, S)
è quasi sempre scritto più chiaro come[x for x in S if P(x)]
, e questo ha l'enorme vantaggio che gli usi più comuni riguardano predicati che sono confronti, ad esempiox==42
, e che definiscono un lambda per quello richiede solo molto più sforzo per il lettore (più il lambda è più lento della comprensione delle liste). Ancora di più per lamap(F, S)
che diventa[F(x) for x in S]
. Ovviamente, in molti casi è possibile utilizzare le espressioni del generatore.
Le seguenti righe di codice sono considerate " non pitoni " e solleveranno errori in molti lint di pitone.
filter(lambda x: x % 2 == 0, range(10)) # even numbers < 10
map(lambda x: 2*x, range(10)) # multiply each number by two
reduce(lambda x,y: x+y, range(10)) # sum of all elements in list
Prendendo ciò che abbiamo imparato dalla citazione precedente, possiamo suddividere queste espressioni di filter
e map
nelle loro equivalenti liste di comprensione ; rimuovendo anche le funzioni lambda da ognuna - rendendo il codice più leggibile nel processo.
# Filter:
# P(x) = x % 2 == 0
# S = range(10)
[x for x in range(10) if x % 2 == 0]
# Map
# F(x) = 2*x
# S = range(10)
[2*x for x in range(10)]
La leggibilità diventa ancora più evidente quando si ha a che fare con le funzioni di concatenamento. Dove a causa della leggibilità, i risultati di una funzione mappa o filtro dovrebbero essere passati come risultato al successivo; con casi semplici, questi possono essere sostituiti con una sola lista di comprensione. Inoltre, possiamo facilmente capire dalla comprensione della lista quale sia il risultato del nostro processo, dove c'è un carico cognitivo maggiore nel ragionare sul processo di mappatura e filtro concatenato.
# Map & Filter
filtered = filter(lambda x: x % 2 == 0, range(10))
results = map(lambda x: 2*x, filtered)
# List comprehension
results = [2*x for x in range(10) if x % 2 == 0]
Refactoring - Riferimento rapido
Carta geografica
map(F, S) == [F(x) for x in S]
Filtro
filter(P, S) == [x for x in S if P(x)]
dove F
e P
sono funzioni che trasformano rispettivamente i valori di input e restituiscono un bool
Comprensioni di liste annidate
Le comprensioni delle liste annidate, a differenza delle comprensioni degli elenchi con cicli annidati, sono le Comprensioni delle liste all'interno di una comprensione di lista. L'espressione iniziale può essere qualsiasi espressione arbitraria, inclusa un'altra comprensione di lista.
#List Comprehension with nested loop
[x + y for x in [1, 2, 3] for y in [3, 4, 5]]
#Out: [4, 5, 6, 5, 6, 7, 6, 7, 8]
#Nested List Comprehension
[[x + y for x in [1, 2, 3]] for y in [3, 4, 5]]
#Out: [[4, 5, 6], [5, 6, 7], [6, 7, 8]]
L'esempio annidato è equivalente a
l = []
for y in [3, 4, 5]:
temp = []
for x in [1, 2, 3]:
temp.append(x + y)
l.append(temp)
Un esempio in cui una comprensione annidata può essere utilizzata per trasporre una matrice.
matrix = [[1,2,3],
[4,5,6],
[7,8,9]]
[[row[i] for row in matrix] for i in range(len(matrix))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Come nidificati for
cicli, non c'è limite a come le intese profonde possono essere annidate.
[[[i + j + k for k in 'cd'] for j in 'ab'] for i in '12']
# Out: [[['1ac', '1ad'], ['1bc', '1bd']], [['2ac', '2ad'], ['2bc', '2bd']]]
Iterate due o più liste contemporaneamente all'interno della comprensione delle liste
Per iterare più di due elenchi contemporaneamente nella comprensione di liste , si può usare zip()
come:
>>> list_1 = [1, 2, 3 , 4]
>>> list_2 = ['a', 'b', 'c', 'd']
>>> list_3 = ['6', '7', '8', '9']
# Two lists
>>> [(i, j) for i, j in zip(list_1, list_2)]
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
# Three lists
>>> [(i, j, k) for i, j, k in zip(list_1, list_2, list_3)]
[(1, 'a', '6'), (2, 'b', '7'), (3, 'c', '8'), (4, 'd', '9')]
# so on ...