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 a itertools.izip () o
  • range () su xrange () 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 )

È stato utile?

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.

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