English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
In questo articolo, imparerai come utilizzare i generatori Python per creare iterazioni con facilità, quali sono le differenze tra iteratori e funzioni convenzionali e perché è utile utilizzarli.
Con PythonCostruireIteratoreCi sono molti costi; dobbiamo implementare una classe utilizzando i metodi __iter__() e __next__(), tracciare lo stato interno, sollevare StopIteration quando non ci sono valori da restituire, ecc.
Questo è sia lungo che controintuitivo. I generatori possono essere utili in questo caso.
I generatori in Python sono un metodo semplice per creare iteratori. Tutti i costi menzionati in precedenza vengono automaticamente gestiti dai generatori di Python.
In breve, un generatore è una funzione che restituisce un oggetto (iteratore) che possiamo iterare (un valore alla volta).
Creare generatori in Python è molto semplice. È facile come definire una funzione comune utilizzando l'istruzione yield invece dell'istruzione return.
Se una funzione contiene almeno una istruzione yield (può contenere altre istruzioni yield o return), diventa una funzione generatrice. Both yield and return restituiscono alcuni valori dalla funzione.
La differenza sta nel fatto che, quando l'istruzione return termina completamente una funzione, l'istruzione yield pausa la funzione salvando il suo stato, per continuare l'esecuzione in successivi chiamate.
La differenza tra la funzione generatrice eFunzione regolareDifferenze.
Una funzione generatrice contiene una o più istruzioni yield.
Quando viene chiamato, ritorna un oggetto (iteratore), ma non inizia immediatamente l'esecuzione.
Metodi come __iter__() e __next__() vengono implementati automaticamente. Pertanto, possiamo utilizzare next() per esplorare gli elementi.
Una volta che la funzione ha prodotto un risultato, la funzione si ferma e il controllo viene trasferito all'utente.
Le variabili locali e il loro stato vengono ricordati tra le chiamate consecutive.
Infine, quando la funzione termina, viene automaticamente sollevato StopIteration durante ulteriori chiamate.
Questo è un esempio per illustrare tutti i punti sopra menzionati. Abbiamo una funzione generatrice my_gen() denominata con più istruzioni yield.
# Una funzione generatrice semplice def my_gen(): n = 1 print('Questo è il primo print') # La funzione generatrice contiene istruzioni yield yield n n += 1 print('Questo è il secondo print') yield n n += 1 print('Questo è l'ultimo print') yield n
Esempio di esecuzione interattiva nell'interprete come segue. Esegui questi comandi nel Python Shell per visualizzare l'output.
>>> # Ritorna un oggetto, ma non inizia immediatamente l'esecuzione. >>> a = my_gen() >>> # Possiamo utilizzare next() per esplorare questi elementi. >>> Questo è il primo print 1 >>> # Una volta che la funzione ha prodotto un risultato, la funzione si ferma e il controllo viene trasferito all'utente. >>> # Le variabili locali e il loro stato vengono ricordati tra le chiamate consecutive. >>> Questo è il secondo print 2 >>> Questo è l'ultimo print 3 >>> # Infine, quando la funzione termina, viene automaticamente sollevato StopIteration durante ulteriori chiamate. >>> >>> next(a) Traceback (chiamata più recente in fondo): ... >>> >>> next(a) Traceback (chiamata più recente in fondo): ...
StopIterationUn fatto interessante da notare nell'esempio sopra è che tra una chiamata e l'altra viene ricordata la variabiledei valori.
Diversamente da una funzione comune, le variabili locali non vengono distrutte quando la funzione viene generata. Inoltre, l'oggetto generatore può essere iterato solo una volta.
Per riavviare il processo, dobbiamo creare un altro oggetto generatore utilizzando qualcosa come = my_gen().
Attenzione:L'ultimo punto da notare è che possiamo direttamente utilizzare il generatore conCiclo forInsieme.
Questo è perché il ciclo for accetta un iteratore e lo itera utilizzando la funzione next(). Quando viene attivata StopIteration, si conclude automaticamente.Comprendere come implementare il ciclo for in Python.
# Una funzione generatrice semplice def my_gen(): n = 1 print('Questo è il primo print') # La funzione generatrice contiene istruzioni yield yield n n += 1 print('Questo è il secondo print') yield n n += 1 print('Questo è l'ultimo print') yield n # Utilizzo del ciclo for for item in my_gen(): print(item)
Quando si esegue il programma, l'output è:
Questo è il primo print 1 Questo è il secondo print 2 Questo è l'ultimo print 3
L'esempio sopra è piuttosto inutile, lo studiamo solo per comprendere ciò che accade in sottofondo.
Di solito, le funzioni generatrici sono implementate tramite cicli con condizioni di terminazione appropriate.
Prendiamo come esempio un generatore per invertire una stringa.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str[i] # Ciclo for per invertire una stringa # Output: # o # l # l # e # h for char in rev_str("hello"): print(char)
In questo esempio, utilizziamo la funzione range() per ottenere l'indice in senso inverso tramite un ciclo for.
A prova di fatto, questa funzione generatrice non si limita ai stringhe, ma è applicabile anche a altri tipi di oggetti iterabili, comelist,tuplee così via.
L'uso di espressioni generatori può creare generatori semplici in modo dinamico. Facilita la costruzione di generatori.
Creazione di funzioni lambdafunzioni anonimee crea funzioni generatrici anonime.
La sintassi delle espressioni generatori è simile aPythoninComprensione di listaSintassi. Ma sostituisci gli apici quadri con i parentesi tonde.
La principale differenza tra la comprensione di lista e le espressioni generatori sta nel fatto che, mentre la comprensione di lista genera l'intera lista, le espressioni generatori generano un elemento alla volta.
Sono un po' pigri, generano elementi solo quando necessario. Per questo motivo, le espressioni generatori sono molto più efficienti in termini di memoria rispetto alle comprensioni di lista equivalenti.
# Inizializzazione lista my_list = [1, 3, 6, 10] # Utilizzo della comprensione di lista per quadruplicare ogni elemento # Output: [1, 9, 36, 100] [x**2 for x in my_list] # La stessa cosa può essere fatta con un'espressione generatrice # Output: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list)
Come possiamo vedere, l'espressione generatrice non produce immediatamente il risultato desiderato. Al contrario, restituisce un oggetto generatore che produce elementi su richiesta.
# Inizializzazione lista my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # Output: 1 print(next(a)) # Output: 9 print(next(a)) # Output: 36 print(next(a)) # Output: 100 print(next(a)) # Output: StopIteration next(a)
Le espressioni generatori possono essere utilizzate all'interno di funzioni. Quando utilizzate in questo modo, è possibile rimuovere le parentesi tonde.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Ci sono diversi motivi che rendono i generatori una soluzione attrattiva.
Le generatori possono essere implementati in modo chiaro e conciso rispetto agli equivalenti delle classi iteratori. Ecco un esempio di sequenza di potenze di 2 implementata con la classe iterator.
class PowTwo: def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n > self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
Questo codice è molto lungo. Ora, esegui la stessa operazione utilizzando la funzione generatore.
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
Poiché i generatori tracciano automaticamente i dettagli, sono chiari e più semplici da implementare.
Una funzione comune che restituisce una sequenza crea l'intera sequenza in memoria prima di restituire i risultati. Se il numero di elementi nella sequenza è molto grande, può influenzare l'efficienza.
L'implementazione del generatore di questa sequenza è amica della memoria, quindi è la scelta preferita perché può generare solo un elemento alla volta.
I generatori sono un ottimo medium per rappresentare flussi di dati infiniti. I flussi infiniti non possono essere memorizzati in memoria e poiché i generatori generano un elemento alla volta, possono rappresentare flussi infiniti.
Esempi di seguito possono generare tutti gli numeri pari (almeno teoricamente).
def all_even(): n = 0 while True: yield n n += 2
I generatori possono essere utilizzati per pipeline di una serie di operazioni. Meglio con un esempio per illustrarlo.
Supponiamo di avere un file di log di una nota catena di fast food. Il file di log contiene una colonna (colonna quarta), che traccia il numero di pizza vendute ogni ora, e vogliamo sommarla per ottenere il numero totale di pizza vendute negli ultimi 5 anni.
Supponiamo che tutto il contenuto sia una stringa, senza numeri disponibili contrassegnati come "N / A". L'implementazione del generatore può essere come segue.
with open('sells.log') as file: pizza_col = (line[3] for line in file) per_hour = (int(x) for x in pizza_col if x != 'N/A') print("Totali di pizza vendute = ", sum(per_hour))
Questa pipeline è efficiente e facile da leggere (sì, è molto cool!).