Quando non è un buon momento per usare i generatori di Python?
-
05-07-2019 - |
Domanda
Questo è piuttosto l'inverso di Cosa puoi usare le funzioni del generatore Python per? : generatori di python, espressioni di generatori e modulo itertools
sono alcune delle mie caratteristiche preferite di python in questi giorni. Sono particolarmente utili quando si configurano catene di operazioni da eseguire su una grande quantità di dati: le utilizzo spesso durante l'elaborazione di file DSV.
Quindi quando non è un buon momento per usare un generatore, un'espressione di generatore o una funzione itertools
?
- Quando dovrei preferire
zip ()
rispetto aitertools.izip ()
o -
range ()
suxrange ()
o -
[x for x in foo]
su(x for x in foo)
?
Ovviamente, alla fine dovremo "risolvere" " un generatore in dati reali, di solito creando un elenco o iterando su di esso con un ciclo non generatore. A volte abbiamo solo bisogno di sapere la lunghezza. Questo non è quello che sto chiedendo.
Utilizziamo generatori in modo da non assegnare nuovi elenchi in memoria per i dati provvisori. Ciò ha senso soprattutto per set di dati di grandi dimensioni. Ha senso anche per piccoli set di dati? Esiste un notevole compromesso tra memoria e CPU?
Sono particolarmente interessato se qualcuno ha fatto un po 'di profiling su questo, alla luce della discussione aperta su elenca le prestazioni di comprensione vs. map () e filter () . ( alt link )
Soluzione
Utilizza un elenco anziché un generatore quando:
1) È necessario accedere ai dati più volte (ovvero memorizzare nella cache i risultati anziché ricalcolarli):
for i in outer: # used once, okay to be a generator or return a list
for j in inner: # used multiple times, reusing a list is better
...
2) È necessario accesso casuale (o qualsiasi accesso diverso dall'ordine sequenziale in avanti):
for i in reversed(data): ... # generators aren't reversible
s[i], s[j] = s[j], s[i] # generators aren't indexable
3) Devi unire stringhe (che richiedono due passaggi sui dati):
s = ''.join(data) # lists are faster than generators in this use case
4) Stai utilizzando PyPy che a volte non è in grado di ottimizzare il codice del generatore quanto può con le normali chiamate di funzione e manipolazioni di elenchi.
Altri suggerimenti
In generale, non utilizzare un generatore quando sono necessarie operazioni di elenco, come len (), reversed () e così via.
Potrebbero anche verificarsi momenti in cui non si desidera una valutazione pigra (ad es. per eseguire tutti i calcoli in anticipo in modo da poter rilasciare una risorsa). In tal caso, un'espressione di elenco potrebbe essere migliore.
Profilo, Profilo, Profilo.
La profilazione del tuo codice è l'unico modo per sapere se ciò che stai facendo ha qualche effetto.
La maggior parte degli usi di xrange, generatori, ecc. sono di dimensioni statiche, piccoli set di dati. È solo quando si arriva a set di dati di grandi dimensioni che fa davvero la differenza. range () vs. xrange () è principalmente solo una questione di rendere il codice un po 'più brutto, non perdere nulla e forse guadagnare qualcosa.
Profilo, Profilo, Profilo.
Non dovresti mai favorire zip
over izip
, < code> range su xrange
o elenca le comprensioni sulle comprensioni del generatore. In Python 3.0 range
ha xrange
-come semantica e zip
ha izip
-come semantica.
Le comprensioni dell'elenco sono in realtà più chiare come list (frob (x) per x in foo)
per quelle volte in cui hai bisogno di un elenco reale.
Come dici tu, "Questo ha particolarmente senso per grandi set di dati", penso che questo risponda alla tua domanda.
Se non colpisci alcun muro, dal punto di vista delle prestazioni, puoi comunque attenerti agli elenchi e alle funzioni standard. Quindi, quando si verificano problemi con le prestazioni, eseguire il passaggio.
Come menzionato da @ u0b34a0f6ae nei commenti, tuttavia, l'utilizzo di generatori all'inizio può semplificare il ridimensionamento a set di dati più grandi.
Per quanto riguarda le prestazioni: se si utilizza psyco, le liste possono essere un po 'più veloci dei generatori. Nell'esempio seguente, gli elenchi sono quasi il 50% più veloci quando si utilizza psyco.full ()
import psyco
import time
import cStringIO
def time_func(func):
"""The amount of time it requires func to run"""
start = time.clock()
func()
return time.clock() - start
def fizzbuzz(num):
"""That algorithm we all know and love"""
if not num % 3 and not num % 5:
return "%d fizz buzz" % num
elif not num % 3:
return "%d fizz" % num
elif not num % 5:
return "%d buzz" % num
return None
def with_list(num):
"""Try getting fizzbuzz with a list comprehension and range"""
out = cStringIO.StringIO()
for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
print >> out, fibby
return out.getvalue()
def with_genx(num):
"""Try getting fizzbuzz with generator expression and xrange"""
out = cStringIO.StringIO()
for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
print >> out, fibby
return out.getvalue()
def main():
"""
Test speed of generator expressions versus list comprehensions,
with and without psyco.
"""
#our variables
nums = [10000, 100000]
funcs = [with_list, with_genx]
# try without psyco 1st
print "without psyco"
for num in nums:
print " number:", num
for func in funcs:
print func.__name__, time_func(lambda : func(num)), "seconds"
print
# now with psyco
print "with psyco"
psyco.full()
for num in nums:
print " number:", num
for func in funcs:
print func.__name__, time_func(lambda : func(num)), "seconds"
print
if __name__ == "__main__":
main()
Risultati:
without psyco
number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds
number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds
with psyco
number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds
number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds
Per quanto riguarda le prestazioni, non riesco a pensare a nessuna volta che vorresti usare un elenco su un generatore.
Non ho mai trovato una situazione in cui i generatori avrebbero ostacolato ciò che stai cercando di fare. Ci sono, tuttavia, molti casi in cui l'uso di generatori non ti aiuterebbe più che non usarli.
Ad esempio:
sorted(xrange(5))
Non offre alcun miglioramento rispetto a:
sorted(range(5))
Dovresti preferire la comprensione dell'elenco se devi conservare i valori per qualcos'altro in seguito e la dimensione del tuo set non è troppo grande.
Ad esempio: stai creando un elenco che ripeterai più volte nel programma.
In una certa misura si può pensare ai generatori come a una sostituzione dell'iterazione (loop) e alla comprensione delle liste come a un tipo di inizializzazione della struttura dei dati. Se si desidera mantenere la struttura dei dati, utilizzare la comprensione dell'elenco.