Python Language
Comprensione delle liste
Ricerca…
introduzione
Le comprensioni delle liste in Python sono costrutti concisi e sintattici. Possono essere utilizzati per generare elenchi da altri elenchi applicando funzioni a ciascun elemento nell'elenco. La seguente sezione spiega e dimostra l'uso di queste espressioni.
Sintassi
- [x + 1 per x in (1, 2, 3)] # list comprehension, dà [2, 3, 4]
- (x + 1 per x in (1, 2, 3)) espressione del generatore #, darà 2, quindi 3, quindi 4
- [x per x in (1, 2, 3) se x% 2 == 0] # list comprehension with filter, dà [2]
- [x + 1 if x% 2 == 0 else x per x in (1, 2, 3)] # list comprehension with ternary
- [x + 1 if x% 2 == 0 else x per x nel range (-3,4) se x> 0] # list comprehension with ternary e filtering
- {x per x in (1, 2, 2, 3)} # set comprehension, dà {1, 2, 3}
- {k: v per k, v in [('a', 1), ('b', 2)]} # dict comprensione, dà {'a': 1, 'b': 2} (python 2.7+ e Solo 3.0+)
- [x + y per x in [1, 2] per y in [10, 20]] # Anelli nidificati, restituisce [11, 21, 12, 22]
- [x + y per x in [1, 2, 3] se x> 2 per y in [3, 4, 5]] # Condizione verificata al 1 ° ciclo
- [x + y per x in [1, 2, 3] per y in [3, 4, 5] se x> 2] # Condizione selezionata al 2 per ciclo
- [x per x in xrange (10) if x% 2 == 0] # Condition checked if numbers in loop sono numeri dispari
Osservazioni
Le comprensioni sono costrutti sintattici che definiscono strutture dati o espressioni uniche per un particolare linguaggio. L'uso corretto delle comprensioni reinterpreta questi in espressioni facilmente comprensibili. Come espressioni, possono essere utilizzate:
- nella parte destra dei compiti
- come argomenti per funzionare chiamate
- nel corpo di una funzione lambda
- come dichiarazioni autonome. (Ad esempio:
[print(x) for x in range(10)]
)
Elenco delle comprensioni
Una comprensione delle liste crea una nuova list
applicando un'espressione a ciascun elemento di un iterabile . La forma più elementare è:
[ <expression> for <element> in <iterable> ]
C'è anche una condizione opzionale "se":
[ <expression> for <element> in <iterable> if <condition> ]
Ogni <element>
in <iterable>
è collegato a <expression>
se la ( <condition>
) <condition>
valutata come vera . Tutti i risultati vengono restituiti contemporaneamente nella nuova lista. Le espressioni del generatore vengono valutate pigramente, ma la comprensione delle liste valuta immediatamente l'intero iteratore - consumando memoria proporzionale alla lunghezza dell'iteratore.
Per creare un list
di interi quadrati:
squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]
L'espressione for
imposta x
per ciascun valore a sua volta da (1, 2, 3, 4)
. Il risultato dell'espressione x * x
è aggiunto ad una list
interna. L' list
interno viene assegnato ai squares
variabili quando completato.
Oltre ad un aumento di velocità (come spiegato qui ), una comprensione di lista è approssimativamente equivalente al seguente ciclo:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
L'espressione applicata a ciascun elemento può essere complessa quanto necessaria:
# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
altro
else
può essere usato nei costrutti di comprensione delle liste, ma attenzione alla sintassi. Le clausole if / else devono essere usate prima for
ciclo, non dopo:
# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax
# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']
Nota: utilizza un costrutto linguistico diverso, un'espressione condizionale , che a sua volta non fa parte della sintassi di comprensione . Mentre il if
dopo il for…in
è una parte delle list comprehensions e viene usato per filtrare elementi dalla sorgente iterabile.
Doppia iterazione
L'ordine di doppia iterazione [... for x in ... for y in ...]
è naturale o contro-intuitivo. La regola generale è quella di seguire un equivalente for
ciclo:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
Questo diventa:
[str(x)
for i in range(3)
for x in foo(i)
]
Questo può essere compresso in una riga come [str(x) for i in range(3) for x in foo(i)]
Mutazione sul posto e altri effetti collaterali
Prima di utilizzare la comprensione delle liste, comprendere la differenza tra le funzioni chiamate per i loro effetti collaterali ( mutanti o funzioni sul posto ) che in genere restituiscono None
e le funzioni che restituiscono un valore interessante.
Molte funzioni (in particolare le funzioni pure ) prendono semplicemente un oggetto e restituiscono un oggetto. Una funzione sul posto modifica l'oggetto esistente, che è chiamato effetto collaterale . Altri esempi includono operazioni di input e output come la stampa.
list.sort()
ordina un elenco sul posto (nel senso che modifica l'elenco originale) e restituisce il valore None
. Pertanto, non funzionerà come previsto in una comprensione di lista:
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
Invece, sorted()
restituisce una list
ordinata piuttosto che ordinare sul posto:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]
È possibile utilizzare la comprensione per gli effetti collaterali, come I / O o le funzioni sul posto. Tuttavia, un ciclo for è solitamente più leggibile. Mentre questo funziona in Python 3:
[print(x) for x in (1, 2, 3)]
Usa invece:
for x in (1, 2, 3):
print(x)
In alcune situazioni, le funzioni di effetti collaterali sono adatte per la comprensione delle liste. random.randrange()
ha l'effetto collaterale di cambiare lo stato del generatore di numeri casuali, ma restituisce anche un valore interessante. Inoltre, next()
può essere chiamato su un iteratore.
Il seguente generatore di valori casuali non è puro, ma ha senso quando il generatore casuale viene reimpostato ogni volta che viene valutata l'espressione:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
Spazio bianco nella comprensione delle liste
Comprensioni di lista più complicate possono raggiungere una lunghezza indesiderata o diventare meno leggibili. Sebbene meno comune negli esempi, è possibile rompere una comprensione di lista in più linee in questo modo:
[
x for x
in 'foo'
if x not in 'bar'
]
Comprensioni del dizionario
Una comprensione del dizionario è simile alla comprensione di una lista, tranne per il fatto che produce un oggetto dizionario invece di una lista.
Un esempio di base:
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
che è solo un altro modo di scrivere:
dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Come per la comprensione di una lista, possiamo usare una dichiarazione condizionale all'interno della comprensione del ditt per produrre solo gli elementi dict che soddisfano qualche criterio.
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
# Out: {'Exchange': 8, 'Overflow': 8}
Oppure, riscritto usando un'espressione di generatore.
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}
A partire da un dizionario e usando la comprensione del dizionario come filtro della coppia chiave-valore
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}
Commutazione chiave e valore del dizionario (dizionario invertito)
Se hai un dict contenente semplici valori hashable (i valori duplicati potrebbero avere risultati imprevisti):
my_dict = {1: 'a', 2: 'b', 3: 'c'}
e volevi scambiare le chiavi e i valori, puoi adottare diversi approcci a seconda del tuo stile di codifica:
-
swapped = {v: k for k, v in my_dict.items()}
-
swapped = dict((v, k) for k, v in my_dict.iteritems())
-
swapped = dict(zip(my_dict.values(), my_dict))
-
swapped = dict(zip(my_dict.values(), my_dict.keys()))
-
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Se il tuo dizionario è grande, considera l' importazione di itertools e utilizza izip
o imap
.
Unione di dizionari
Combina dizionari e opzionalmente sostituisci i vecchi valori con una comprensione del dizionario annidata.
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Tuttavia, il disimballaggio del dizionario ( PEP 448 ) potrebbe essere preferito.
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Nota : le comprensibilità del dizionario sono state aggiunte in Python 3.0 e supportate a 2.7+, a differenza delle list comprehensions, che sono state aggiunte in 2.0. Le versioni <2.7 possono usare le espressioni del generatore e il dict()
incorporato per simulare il comportamento delle comprensioni del dizionario.
Espressioni del generatore
Le espressioni del generatore sono molto simili alle list comprehensions. La differenza principale è che non crea una serie completa di risultati contemporaneamente; crea un oggetto generatore che può quindi essere iterato sopra.
Ad esempio, vedere la differenza nel seguente codice:
# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>
Questi sono due oggetti molto diversi:
la comprensione della lista restituisce un oggetto
list
mentre la comprensione del generatore restituisce ungenerator
.generator
oggetti delgenerator
non possono essere indicizzati e si avvale della funzionenext
per ottenere gli oggetti in ordine.
Nota : usiamo xrange
poiché crea anche un oggetto generatore. Se usassimo l'intervallo, sarebbe creata una lista. Inoltre, xrange
esiste solo nella versione successiva di python 2. In python 3, l' range
restituisce solo un generatore. Per ulteriori informazioni, vedere l'esempio delle differenze tra intervallo e xrange .
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'
g.next() # 0
g.next() # 1
g.next() # 4
...
g.next() # 81
g.next() # Throws StopIteration Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
NOTA: la funzione
g.next()
dovrebbe essere sostituita danext(g)
exrange
conrange
poichéIterator.next()
exrange()
non esistono in Python 3.
Sebbene entrambi possano essere ripetuti in modo simile:
for i in [x**2 for x in range(10)]:
print(i)
"""
Out:
0
1
4
...
81
"""
for i in (x**2 for x in xrange(10)):
print(i)
"""
Out:
0
1
4
.
.
.
81
"""
Casi d'uso
Le espressioni del generatore vengono valutate pigramente, il che significa che generano e restituiscono ciascun valore solo quando il generatore viene iterato. Ciò è spesso utile durante l'iterazione di set di dati di grandi dimensioni, evitando la necessità di creare un duplicato del set di dati nella memoria:
for square in (x**2 for x in range(1000000)):
#do something
Un altro caso d'uso comune è quello di evitare di iterare su un intero iterabile se ciò non è necessario. In questo esempio, un elemento viene recuperato da un'API remota con ogni iterazione di get_objects()
. Migliaia di oggetti possono esistere, devono essere recuperati uno alla volta e abbiamo solo bisogno di sapere se esiste un oggetto che corrisponde a un modello. Usando un'espressione di generatore, quando incontriamo un oggetto che corrisponde al modello.
def get_objects():
"""Gets objects from an API one by one"""
while True:
yield get_next_item()
def object_matches_pattern(obj):
# perform potentially complex calculation
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Imposta Comprensioni
La comprensione dell'insieme è simile alla comprensione di elenchi e dizionari , ma produce un insieme , che è una collezione non ordinata di elementi unici.
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}
# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}
# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
Tieni presente che i set non sono ordinati. Ciò significa che l'ordine dei risultati nell'insieme può essere diverso da quello presentato negli esempi precedenti.
Nota : la comprensione dei set è disponibile da python 2.7+, a differenza delle list comprehensions, che sono state aggiunte in 2.0. In Python 2.2 a Python 2.6, la funzione set()
può essere utilizzata con un'espressione generatore per produrre lo stesso risultato:
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}
Evita operazioni ripetitive e costose usando la clausola condizionale
Considera la sotto lista di comprensione:
>>> def f(x):
... import time
... time.sleep(.1) # Simulate expensive function
... return x**2
>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]
Ciò comporta due chiamate a f(x)
per 1.000 valori di x
: una chiamata per la generazione del valore e l'altra per il controllo della condizione if
. Se f(x)
è un'operazione particolarmente costosa, ciò può avere implicazioni significative sulle prestazioni. Peggio ancora, se chiamare f()
ha effetti collaterali, può avere risultati sorprendenti.
Invece, dovresti valutare l'operazione costosa solo una volta per ogni valore di x
generando un iterabile intermedio ( espressione del generatore ) come segue:
>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]
Oppure, usando l'equivalente della mappa integrata:
>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]
Un altro modo che potrebbe risultare in un codice più leggibile consiste nel mettere il risultato parziale ( v
nell'esempio precedente) in un iterabile (come un elenco o una tupla) e quindi scorrere su di esso. Dato che v
sarà l'unico elemento nel iterabile, il risultato è che ora abbiamo un riferimento all'output della nostra funzione lenta calcolata una sola volta:
>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]
Tuttavia, in pratica, la logica del codice può essere più complicata ed è importante tenerlo leggibile. In generale, una funzione di generatore separata è raccomandata su un one-liner complesso:
>>> def process_prime_numbers(iterable):
... for x in iterable:
... if is_prime(x):
... yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]
Un altro modo per evitare che il calcolo f(x)
più volte è quello di utilizzare il @functools.lru_cache()
(Python 3.2+) decoratore su f(x)
. In questo modo, poiché l'output di f
per l'input x
è già stato calcolato una volta, la seconda funzione invocata dalla comprensione dell'elenco originale sarà veloce quanto una ricerca di dizionario. Questo approccio utilizza la memoizzazione per migliorare l'efficienza, che è paragonabile all'uso di espressioni generatrici.
Di 'che devi appiattire una lista
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Alcuni dei metodi potrebbero essere:
reduce(lambda x, y: x+y, l)
sum(l, [])
list(itertools.chain(*l))
Tuttavia la comprensione delle liste fornirebbe la migliore complessità temporale.
[item for sublist in l for item in sublist]
Le scorciatoie basate su + (incluso l'uso implicito in somma) sono, per necessità, O (L ^ 2) quando ci sono sottoliste L - poiché l'elenco dei risultati intermedi continua ad allungarsi, ad ogni passaggio viene ottenuto un nuovo elenco di risultati intermedi assegnato e tutti gli elementi nel risultato intermedio precedente devono essere copiati sopra (così come alcuni nuovi aggiunti alla fine). Quindi (per semplicità e senza effettiva perdita di generalità) dite di avere L Liste di elementi I ciascuno: i primi elementi I vengono copiati avanti e indietro L-1 volte, il secondo I elementi L-2 volte, e così via; il numero totale di copie è I volte la somma di x per x da 1 a L esclusa, cioè I * (L ** 2) / 2.
La comprensione dell'elenco genera solo una lista, una volta, e copia ogni oggetto (dalla sua posizione originale di residenza alla lista dei risultati) anche esattamente una volta.
Comprensioni che riguardano le tuple
La clausola for
di una list list può specificare più di una variabile:
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]
Questo è come regolare for
loop:
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
# 3
# 7
# 11
Nota comunque, se l'espressione che inizia la comprensione è una tupla, allora deve essere tra parentesi:
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]
Conteggio delle ricorrenze utilizzando la comprensione
Quando vogliamo contare il numero di elementi in un iterabile, che soddisfano alcune condizioni, possiamo usare la comprensione per produrre una sintassi idiomatica:
# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
# Out: 95
Il concetto di base può essere riassunto come:
- Iterare sugli elementi
range(1000)
. - Concatenate tutto il necessario
if
le condizioni. - Usa 1 come espressione per restituire 1 per ogni oggetto che soddisfa le condizioni.
- Riassumi tutti i
1
s per determinare il numero di articoli che soddisfano le condizioni.
Nota : qui non stiamo raccogliendo i 1
s in una lista (notare l'assenza di parentesi quadre), ma stiamo passando quelli direttamente alla funzione sum
che li sta sommando. Questo è chiamato un'espressione generatore , che è simile a una comprensione.
Modifica dei tipi in un elenco
I dati quantitativi vengono spesso letti come stringhe che devono essere convertite in tipi numerici prima dell'elaborazione. I tipi di tutti gli elementi dell'elenco possono essere convertiti con una funzione Comprehension elenco o map()
.
# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]
# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0]