Domanda

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)]
È stato utile?

Soluzione

La risposta di John è buona (la comprensione dell'elenco è migliore quando si desidera ripetere qualcosa più volte).Tuttavia, vale anche la pena notare che dovresti utilizzare un elenco se desideri utilizzare uno qualsiasi 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 è ripetere una volta.Se desideri archiviare e utilizzare i risultati generati, probabilmente ti conviene utilizzare una lista di comprensione.

Poiché la prestazione è il motivo più comune per sceglierne uno rispetto all'altro, il mio consiglio è di non preoccuparsene e di sceglierne semplicemente uno;se scopri che il tuo programma sta funzionando troppo lentamente, allora e solo allora dovresti tornare indietro e preoccuparti di ottimizzare il tuo codice.

Altri suggerimenti

Iterando su espressione del generatore o il comprensione delle liste farà la stessa cosa.comunque, il comprensione delle liste creerà prima l'intero elenco in memoria mentre il espressione del generatore creerà gli elementi al volo, quindi potrai usarlo per sequenze molto grandi (e anche infinite!).

Utilizza le comprensioni delle liste quando il risultato deve essere ripetuto più volte o dove la velocità è fondamentale.Utilizza espressioni del generatore in cui l'intervallo è ampio o infinito.

Il punto importante è che la comprensione delle liste crea una nuova lista.Il generatore crea un oggetto iterabile che "filtrerà" il materiale sorgente al volo mentre consumi i bit.

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

Quindi provi a iniziare scrivendo una comprensione dell'elenco:

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

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

Quindi possiamo invece utilizzare un generatore per applicare un "filtro" al nostro contenuto.Nessun dato viene effettivamente letto finché non iniziamo a ripetere il risultato.

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

Non è stata ancora letta nemmeno una riga dal nostro file.Infatti, supponiamo di voler filtrare ulteriormente il nostro risultato:

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

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

Scriviamo le nostre righe filtrate in un altro file:

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

Ora leggiamo il file di input.Come il nostro for il ciclo continua a richiedere righe aggiuntive, il long_entries il generatore richiede linee da entry_lines generatore, restituendo solo quelli la cui lunghezza è maggiore di 80 caratteri.E a sua volta, il entry_lines il generatore richiede linee (filtrate come indicato) dal file logfile iteratore, che a sua volta legge il file.

Quindi, invece di "inviare" i dati alla funzione di output sotto forma di un elenco completamente popolato, stai dando 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, un passaggio;i dati del file di log che abbiamo letto vengono immediatamente scartati, quindi non possiamo tornare alla riga precedente.D'altra parte, non dobbiamo preoccuparci di conservare i dati una volta che li abbiamo finiti.

Il vantaggio di un'espressione generatrice è che utilizza meno memoria poiché non crea l'intero elenco in una volta.Le espressioni del generatore sono utilizzate al meglio quando l'elenco funge da intermediario, ad esempio sommando i risultati o creando un dict 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 viene generato completamente e quindi viene utilizzata poca memoria (e dovrebbe anche essere più veloce)

Dovresti, tuttavia, utilizzare le comprensioni degli elenchi quando il prodotto finale desiderato è un elenco.Non salverai alcun ricordo utilizzando le espressioni del generatore, poiché vuoi l'elenco generato.Hai anche il vantaggio di poter utilizzare qualsiasi funzione dell'elenco come ordinato o invertito.

Per esempio:

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

Quando crei un generatore da un oggetto mutabile (come una lista), tieni presente che il generatore verrà valutato in base allo stato della lista 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 il tuo elenco venga modificato (o un oggetto mutabile all'interno di quell'elenco) ma hai bisogno dello stato al momento della creazione del generatore, devi invece utilizzare una comprensione dell'elenco.

Sto usando il Modulo Hadoop carne macinata.Penso che questo sia un ottimo esempio da prendere in considerazione:

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 estrae i numeri da un file di testo (grande fino a 15 GB) e applica semplici calcoli matematici a tali numeri utilizzando la riduzione della mappa di Hadoop.Se non avessi utilizzato la funzione di rendimento, ma invece una comprensione di lista, ci sarebbe voluto molto più tempo per calcolare le somme e la media (per non parlare della complessità dello spazio).

Hadoop è un ottimo esempio di come sfruttare tutti i vantaggi dei generatori.

A volte puoi farla franca con il tee funzione da itertools, restituisce più iteratori per lo stesso generatore che possono essere utilizzati in modo indipendente.

che ne dici di usare [(exp for x in iter)] per ottenere il bene di entrambi.Prestazioni dalla comprensione del generatore e dai metodi di elenco

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top