Espressioni del generatore e comprensione dell'elenco


412

Quando dovresti usare le espressioni del generatore e quando dovresti usare la comprensione delle liste in Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

28
potrebbe [exp for x in iter]essere solo zucchero per list((exp for x in iter))? o c'è una differenza di esecuzione?
nato il

1
penso di avere una domanda pertinente, quindi quando si usa il rendimento possiamo usare solo l'espressione del generatore da una funzione o dobbiamo usare il rendimento per una funzione per restituire l'oggetto generatore?

28
@ b0fh Risposta molto tardi al tuo commento: in Python2 c'è una piccola differenza, la variabile loop uscirà dalla comprensione di un elenco, mentre un'espressione del generatore non colerà. Confronta X = [x**2 for x in range(5)]; print xcon Y = list(y**2 for y in range(5)); print y, il secondo darà un errore. In Python3, la comprensione di un elenco è in effetti lo zucchero sintattico per un'espressione del generatore alimentata list()come previsto, quindi la variabile loop non sarà più fuoriuscita .
Bas Swinckels,

13
Suggerirei di leggere PEP 0289 . Riassunto da "Questo PEP introduce le espressioni del generatore come una generalizzazione ad alte prestazioni, efficiente in termini di memoria, delle comprensioni e dei generatori di elenchi" . Ha anche esempi utili su quando usarli.
icc97,

5
@ icc97 Sono anche in ritardo di otto anni alla festa e il collegamento PEP era perfetto. Grazie per averlo trovato facilmente!
eenblam,

Risposte:


283

La risposta di John è buona (le comprensioni dell'elenco sono migliori quando si desidera ripetere più volte qualcosa). Tuttavia, vale anche la pena notare che è necessario utilizzare un elenco se si desidera utilizzare uno dei metodi dell'elenco. Ad esempio, il seguente codice non funzionerà:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Fondamentalmente, usa un'espressione del generatore se tutto ciò che stai facendo è iterare una volta. Se desideri archiviare e utilizzare i risultati generati, probabilmente stai meglio con una comprensione dell'elenco.

Poiché la performance è il motivo più comune per sceglierne una rispetto all'altra, il mio consiglio è di non preoccuparsene e di sceglierne una; se scopri che il tuo programma è in esecuzione troppo lentamente, allora e solo allora dovresti tornare indietro e preoccuparti di ottimizzare il tuo codice.


70
A volte è necessario utilizzare generatori - per esempio, se si sta scrivendo coroutine con la programmazione cooperativa dalla resa. Ma se lo stai facendo, probabilmente non stai facendo questa domanda;)
effimero

12
So che questo è vecchio, ma penso che valga la pena notare che i generatori (e tutti gli iterabili) possono essere aggiunti agli elenchi con estensione: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- un test ora sarà [1, 2, 3, 4, 5, 6]. (Puoi aggiungere nuove righe nei commenti ??)
jarvisteve il

12
@jarvisteve il tuo esempio smentisce le parole che stai dicendo. C'è anche un bel punto qui. Gli elenchi possono essere estesi con generatori, ma non ha senso trasformarlo in un generatore. I generatori non possono essere estesi con elenchi e i generatori non sono abbastanza iterabili. a = (x for x in range(0,10)), b = [1,2,3]per esempio. a.extend(b)genera un'eccezione. b.extend(a)valuterà tutto a, nel qual caso non ha senso trasformarlo in un generatore in primo luogo.
Slater Victoroff,

4
@SlaterTyranus hai ragione al 100% e ti ho votato per la precisione. nondimeno, penso che il suo commento sia un'utile non risposta alla domanda del PO perché aiuterà coloro che si trovano qui perché hanno digitato qualcosa come "unisci il generatore con la comprensione dell'elenco" in un motore di ricerca.
rbp

1
Il motivo dell'utilizzo di un generatore per scorrere una volta (ad esempio la mia preoccupazione per la mancanza di memoria ha la precedenza sulla mia preoccupazione di "recuperare i valori" uno alla volta ) probabilmente si applicherebbe ancora quando si ripetono più volte? Direi che potrebbe rendere un elenco più utile, ma se è abbastanza per superare i problemi di memoria è qualcos'altro.
Rob Grant,

181

Iterare sull'espressione del generatore o sulla comprensione dell'elenco farà la stessa cosa. Tuttavia, la comprensione dell'elenco creerà prima l'intero elenco in memoria mentre l' espressione del generatore creerà gli elementi al volo, quindi sarai in grado di usarlo per sequenze molto grandi (e anche infinite!).


39
+1 per infinito. Non puoi farlo con un elenco, indipendentemente da quanto poco ti preoccupi delle prestazioni.
Paul Draper,

Puoi creare infiniti generatori usando il metodo di comprensione?
AnnanFay,

5
@Annan Solo se hai già accesso a un altro generatore infinito. Ad esempio, itertools.count(n)è una sequenza infinita di numeri interi, a partire da n, quindi (2 ** item for item in itertools.count(n))sarebbe una sequenza infinita dei poteri di 2partenza da 2 ** n.
Kevin

2
Un generatore elimina gli elementi dalla memoria dopo che sono stati ripetuti. Quindi è veloce se hai dei big data che vuoi solo mostrarli, per esempio. Non è un porco di memoria. con i generatori gli articoli vengono elaborati "secondo necessità". se si desidera aggrapparsi all'elenco o scorrere nuovamente su di esso (quindi memorizzare gli elementi), utilizzare la comprensione dell'elenco.
j2emanue,

102

Utilizzare la comprensione dell'elenco quando il risultato deve essere ripetuto più volte o quando la velocità è fondamentale. Usa le espressioni del generatore in cui l'intervallo è ampio o infinito.

Vedere Espressioni del generatore e comprensione dell'elenco per ulteriori informazioni.


2
Questo sarà probabilmente un po 'fuori tema, ma purtroppo "non googlable" ... Cosa significherebbe "fondamentale" in questo contesto? Non sono un madrelingua inglese ... :)
Guillermo Ares,

6
@GuillermoAres questo è il risultato diretto di "googling" per significato di fondamentale importanza: più importante di ogni altra cosa; supremo.
Sнаđошƒаӽ

1
Quindi listssono più veloci delle generatorespressioni? Dalla lettura della risposta di dF, si è scoperto che era il contrario.
Hassan Baig,

1
Probabilmente è meglio dire che la comprensione dell'elenco è più veloce quando l'intervallo è piccolo, ma all'aumentare della scala diventa più prezioso calcolare i valori al volo - giusto in tempo per il loro uso. Questo è ciò che fa un'espressione del generatore.
Kyle,

59

Il punto importante è che la comprensione dell'elenco crea un nuovo elenco. Il generatore crea un oggetto iterabile che "filtra" al volo il materiale sorgente mentre si consumano i bit.

Immagina di avere un file di registro da 2 TB chiamato "hugefile.txt" e desideri il contenuto e la lunghezza di tutte le righe che iniziano con la parola "ENTRY".

Quindi prova a iniziare scrivendo una lista di comprensione:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Ciò assorbe l'intero file, elabora ogni riga e memorizza le linee corrispondenti nell'array. Questo array può quindi contenere fino a 2 TB di contenuto. È molta RAM e probabilmente non è pratica per i tuoi scopi.

Quindi invece possiamo usare un generatore per applicare un "filtro" ai nostri contenuti. Nessun dato viene effettivamente letto fino a quando non si inizia a ripetere il risultato.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Nemmeno una singola riga è stata ancora letta dal nostro file. In effetti, supponiamo di voler filtrare ulteriormente il nostro risultato:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Ancora nulla è stato letto, ma ora abbiamo specificato due generatori che agiranno sui nostri dati come desideriamo.

Consente di scrivere le nostre linee filtrate su un altro file:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Ora leggiamo il file di input. Mentre il nostro forloop continua a richiedere linee aggiuntive, il long_entriesgeneratore richiede linee dal entry_linesgeneratore, restituendo solo quelle la cui lunghezza è superiore a 80 caratteri. E a sua volta, il entry_linesgeneratore richiede righe (filtrate come indicato) logfiledall'iteratore, che a sua volta legge il file.

Quindi, invece di "spingere" i dati nella funzione di output sotto forma di un elenco completamente popolato, stai offrendo alla funzione di output un modo per "estrarre" i dati solo quando necessario. Nel nostro caso questo è molto più efficiente, ma non altrettanto flessibile. I generatori sono a senso unico, a passaggio; i dati del file di registro che abbiamo letto vengono immediatamente eliminati, quindi non possiamo tornare a una riga precedente. D'altra parte, non dobbiamo preoccuparci di conservare i dati una volta terminato.


46

Il vantaggio di un'espressione del generatore è che usa meno memoria poiché non crea l'intero elenco in una sola volta. Le espressioni del generatore vengono utilizzate al meglio quando l'elenco è un intermediario, ad esempio sommando i risultati o creando un dettato dai risultati.

Per esempio:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

Il vantaggio è che l'elenco non è completamente generato e quindi viene utilizzata poca memoria (e dovrebbe anche essere più veloce)

Tuttavia, è necessario utilizzare la comprensione dell'elenco quando il prodotto finale desiderato è un elenco. Non hai intenzione di salvare alcun memeory usando le espressioni del generatore, poiché desideri l'elenco generato. Ottieni anche il vantaggio di poter utilizzare una qualsiasi delle funzioni dell'elenco come ordinate o invertite.

Per esempio:

reversed( [x*2 for x in xrange(256)] )

9
C'è un suggerimento per te proprio nel linguaggio che le espressioni del generatore devono essere utilizzate in quel modo. Perdere le parentesi! sum(x*2 for x in xrange(256))
u0b34a0f6ae,

8
sortede reversedfunziona bene su qualsiasi iterabile, espressioni del generatore incluse.
marr75,

1
Se puoi usare 2.7 e versioni successive, quell'esempio dict () apparirebbe meglio come comprensione dict (il PEP per questo è più vecchio di quello delle espressioni del generatore PEP, ma impiegava più tempo a sbarcare)
Jürgen A. Erhard

14

Quando si crea un generatore da un oggetto mutabile (come un elenco), tenere presente che il generatore verrà valutato sullo stato dell'elenco al momento dell'utilizzo del generatore, non al momento della creazione del generatore:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Se c'è qualche possibilità che la tua lista venga modificata (o un oggetto mutabile all'interno di quella lista) ma hai bisogno dello stato al momento della creazione del generatore, devi invece usare una comprensione della lista.


1
E questa dovrebbe essere la risposta accettata. Se i tuoi dati sono più grandi della memoria disponibile, dovresti sempre usare i generatori, sebbene il loop sull'elenco in memoria possa essere più veloce (ma non hai abbastanza memoria per farlo).
Marek Marczak,

4

A volte è possibile cavarsela con la funzione tee da itertools , restituisce più iteratori per lo stesso generatore che può essere utilizzato in modo indipendente.


4

Sto usando il modulo Hadoop Mincemeat . Penso che questo sia un ottimo esempio per prendere nota di:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Qui il generatore ottiene i numeri da un file di testo (fino a 15 GB) e applica la matematica semplice su quei numeri usando la riduzione della mappa di Hadoop. Se non avessi usato la funzione di rendimento, ma invece una comprensione dell'elenco, ci sarebbe voluto molto più tempo per calcolare le somme e la media (per non parlare della complessità dello spazio).

Hadoop è un ottimo esempio per l'utilizzo di tutti i vantaggi dei generatori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.