Ricerca…


introduzione

Una list comprehension è uno strumento sintattico per creare liste in modo naturale e conciso, come illustrato nel seguente codice per creare un elenco di quadrati dei numeri da 1 a 10: [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]

Dimostrazione dal vivo

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]

Dimostrazione dal vivo

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]

Dimostrazione dal vivo

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]

Dimostrazione dal vivo

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]

Dimostrazione dal vivo

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 esempio x==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 la map(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 ...


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow