Qual è la differenza tra iteratori e generatori? Alcuni esempi di quando useresti ogni caso sarebbero utili.
Qual è la differenza tra iteratori e generatori? Alcuni esempi di quando useresti ogni caso sarebbero utili.
Risposte:
iterator
è un concetto più generale: qualsiasi oggetto la cui classe ha un next
metodo ( __next__
in Python 3) e un __iter__
metodo che lo fa return self
.
Ogni generatore è un iteratore, ma non viceversa. Un generatore viene creato chiamando una funzione che ha una o più yield
espressioni ( yield
istruzioni, in Python 2.5 e precedenti), ed è un oggetto che soddisfa la definizione di an di un paragrafo precedente iterator
.
Potresti voler usare un iteratore personalizzato, piuttosto che un generatore, quando hai bisogno di una classe con un comportamento un po 'complesso di mantenimento dello stato, o vuoi esporre altri metodi oltre next
(e __iter__
e __init__
). Molto spesso, un generatore (a volte, per esigenze sufficientemente semplici, un'espressione di generatore ) è sufficiente, ed è più semplice da codificare perché la manutenzione dello stato (entro limiti ragionevoli) è sostanzialmente "fatta per te" dal frame che viene sospeso e ripreso.
Ad esempio, un generatore come:
def squares(start, stop):
for i in range(start, stop):
yield i * i
generator = squares(a, b)
o l'espressione del generatore equivalente (genexp)
generator = (i*i for i in range(a, b))
richiederebbe più codice per la creazione come iteratore personalizzato:
class Squares(object):
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self): return self
def next(self): # __next__ in Python 3
if self.start >= self.stop:
raise StopIteration
current = self.start * self.start
self.start += 1
return current
iterator = Squares(a, b)
Ma, naturalmente, con la classe Squares
potresti facilmente offrire metodi extra, ad es
def current(self):
return self.start
se hai effettivamente bisogno di tale funzionalità aggiuntiva nella tua applicazione.
for ... in ...:
, passato a una funzione, o chiameraiiter.next()
for..in
sintassi. Forse mi mancava qualcosa, ma è stato qualche tempo fa, non ricordo se ho risolto. Grazie!
Qual è la differenza tra iteratori e generatori? Alcuni esempi di quando useresti ogni caso sarebbero utili.
In sintesi: gli iteratori sono oggetti che hanno un __iter__
e un metodo __next__
( next
in Python 2). I generatori forniscono un modo semplice e integrato per creare istanze di Iteratori.
Una funzione con rendimento in essa è ancora una funzione che, quando viene chiamata, restituisce un'istanza di un oggetto generatore:
def a_function():
"when called, returns generator object"
yield
Un'espressione di generatore restituisce anche un generatore:
a_generator = (i for i in range(0))
Per un'esposizione più approfondita ed esempi, continua a leggere.
In particolare, il generatore è un sottotipo di iteratore.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Possiamo creare un generatore in diversi modi. Un modo molto comune e semplice per farlo è con una funzione.
In particolare, una funzione con resa in essa è una funzione che, quando chiamata, restituisce un generatore:
>>> def a_function():
"just a function definition with yield in it"
yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function() # when called
>>> type(a_generator) # returns a generator
<class 'generator'>
E un generatore, di nuovo, è un Iteratore:
>>> isinstance(a_generator, collections.Iterator)
True
Un Iteratore è un Iterabile,
>>> issubclass(collections.Iterator, collections.Iterable)
True
che richiede un __iter__
metodo che restituisce un Iteratore:
>>> collections.Iterable()
Traceback (most recent call last):
File "<pyshell#79>", line 1, in <module>
collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__
Alcuni esempi di iterabili sono le tuple, gli elenchi, i dizionari, i set, i set congelati, le stringhe, le stringhe di byte, gli array di byte, gli intervalli e le visualizzazioni di memoria incorporati:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
next
o__next__
In Python 2:
>>> collections.Iterator()
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next
E in Python 3:
>>> collections.Iterator()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__
Possiamo ottenere gli iteratori dagli oggetti incorporati (o oggetti personalizzati) con la iter
funzione:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
Il __iter__
metodo viene chiamato quando si tenta di utilizzare un oggetto con un ciclo for. Quindi il __next__
metodo viene chiamato sull'oggetto iteratore per estrarre ogni elemento per il ciclo. L'iteratore si alza StopIteration
quando lo hai esaurito e non può essere riutilizzato a quel punto.
Dalla sezione Tipi di generatore della sezione Tipi di iteratore della documentazione Tipi incorporati :
I generatori di Python forniscono un modo conveniente per implementare il protocollo iteratore. Se il
__iter__()
metodo di un oggetto contenitore viene implementato come generatore, restituirà automaticamente un oggetto iteratore (tecnicamente, un oggetto generatore) che fornisce i metodi__iter__()
enext()
[__next__()
in Python 3]. Ulteriori informazioni sui generatori sono disponibili nella documentazione per l'espressione di rendimento.
(Enfasi aggiunta.)
Quindi da questo apprendiamo che i generatori sono un tipo (conveniente) di Iteratore.
È possibile creare oggetti che implementano il protocollo Iterator creando o estendendo il proprio oggetto.
class Yes(collections.Iterator):
def __init__(self, stop):
self.x = 0
self.stop = stop
def __iter__(self):
return self
def next(self):
if self.x < self.stop:
self.x += 1
return 'yes'
else:
# Iterators must raise when done, else considered broken
raise StopIteration
__next__ = next # Python 3 compatibility
Ma è più semplice usare semplicemente un generatore per fare questo:
def yes(stop):
for _ in range(stop):
yield 'yes'
O forse più semplice, un'espressione del generatore (funziona in modo simile alle comprensioni dell'elenco):
yes_expr = ('yes' for _ in range(stop))
Possono essere usati tutti allo stesso modo:
>>> stop = 4
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop),
('yes' for _ in range(stop))):
... print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes
È possibile utilizzare il protocollo Iterator direttamente quando è necessario estendere un oggetto Python come oggetto su cui è possibile iterare.
Tuttavia, nella stragrande maggioranza dei casi, è meglio utilizzare yield
per definire una funzione che restituisce un Iteratore del generatore o considerare le espressioni del generatore.
Infine, si noti che i generatori forniscono ancora più funzionalità come coroutine. Spiego Generatori, insieme alla yield
dichiarazione, in modo approfondito sulla mia risposta a "Che cosa fa la parola chiave" rendimento "?".
iteratori:
Gli Iteratori sono oggetti che utilizzano il next()
metodo per ottenere il valore successivo della sequenza.
generatori:
Un generatore è una funzione che produce o produce una sequenza di valori usando il yield
metodo.
Ogni next()
chiamata di metodo sull'oggetto generatore (ad esempio: f
come nell'esempio seguente) restituita dalla funzione generatore (ad esempio: foo()
funzione nell'esempio seguente), genera il valore successivo in sequenza.
Quando viene chiamata una funzione generatore, restituisce un oggetto generatore senza nemmeno iniziare l'esecuzione della funzione. Quando next()
viene chiamato il metodo per la prima volta, la funzione inizia l'esecuzione fino a quando non raggiunge l'istruzione yield che restituisce il valore prodotto. Il rendimento tiene traccia di ie ricorda l'ultima esecuzione. E la seconda next()
chiamata continua dal valore precedente.
L'esempio seguente mostra l'interazione tra yield e call al metodo successivo sull'oggetto generatore.
>>> def foo():
... print "begin"
... for i in range(3):
... print "before yield", i
... yield i
... print "after yield", i
... print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0 # Control is in for loop
0
>>> f.next()
after yield 0
before yield 1 # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Aggiungere una risposta perché nessuna delle risposte esistenti affronta in modo specifico la confusione nella letteratura ufficiale.
Le funzioni del generatore sono funzioni ordinarie definite utilizzandoyield
invece direturn
. Quando viene chiamata, una funzione del generatore restituisce un oggetto generatore , che è una specie di iteratore - ha unnext()
metodo. Quando si chiamanext()
, viene restituito il valore successivo prodotto dalla funzione generatore.
La funzione o l'oggetto possono essere chiamati "generatore" a seconda del documento sorgente Python che leggi. Il glossario di Python dice le funzioni del generatore, mentre il wiki di Python implica oggetti del generatore. Il tutorial di Python riesce notevolmente a implicare entrambi gli usi nello spazio di tre frasi:
I generatori sono uno strumento semplice e potente per la creazione di iteratori. Sono scritti come normali funzioni ma usano l'istruzione yield ogni volta che vogliono restituire dati. Ogni volta che viene chiamato next (), il generatore riprende da dove era stato interrotto (ricorda tutti i valori dei dati e quale istruzione è stata eseguita l'ultima volta).
Le prime due frasi identificano i generatori con le funzioni del generatore, mentre la terza frase li identifica con oggetti del generatore.
Nonostante tutta questa confusione, si può cercare il riferimento al linguaggio Python per la parola chiara e finale:
L'espressione di snervamento viene utilizzata solo quando si definisce una funzione del generatore e può essere utilizzata solo nel corpo di una definizione di funzione. L'uso di un'espressione di snervamento in una definizione di funzione è sufficiente per far sì che tale definizione crei una funzione generatore anziché una funzione normale.
Quando viene chiamata una funzione generatore, restituisce un iteratore noto come generatore. Quel generatore controlla quindi l'esecuzione di una funzione del generatore.
Quindi, in un uso formale e preciso, "generatore" non qualificato significa oggetto generatore, non funzione generatore.
I riferimenti sopra riportati sono per Python 2 ma il riferimento al linguaggio Python 3 dice la stessa cosa. Tuttavia, il glossario di Python 3 afferma che
generatore ... Di solito si riferisce a una funzione del generatore, ma può riferirsi a un iteratore del generatore in alcuni contesti. Nei casi in cui il significato desiderato non è chiaro, l'uso dei termini completi evita ambiguità.
Ognuno ha una risposta davvero piacevole e dettagliata con esempi e lo apprezzo molto. Volevo solo dare una breve risposta per le persone che non sono ancora abbastanza chiare concettualmente:
Se crei il tuo iteratore, è un po 'coinvolto: devi creare una classe e almeno implementare l'iter e i metodi successivi. Ma cosa succede se non vuoi passare attraverso questa seccatura e vuoi creare rapidamente un iteratore. Fortunatamente, Python fornisce una scorciatoia per definire un iteratore. Tutto quello che devi fare è definire una funzione con almeno 1 chiamata da cedere e ora quando chiami quella funzione restituirà " qualcosa " che agirà come un iteratore (puoi chiamare il metodo successivo e usarlo in un ciclo for). Questo qualcosa ha un nome in Python chiamato Generatore
Spero che questo chiarisca un po '.
Le risposte precedenti mancavano questa aggiunta: un generatore ha un close
metodo, mentre gli iteratori tipici no. Il close
metodo attiva StopIteration
un'eccezione nel generatore, che può essere rilevata in afinally
clausola in quell'iteratore, per avere la possibilità di eseguire un clean-up. Questa astrazione lo rende maggiormente utilizzabile negli iteratori più grandi che semplici. Si può chiudere un generatore come si potrebbe chiudere un file, senza doversi preoccupare di cosa c'è sotto.
Detto questo, la mia risposta personale alla prima domanda sarebbe: iteratable ha __iter__
solo un metodo, gli iteratori tipici hanno __next__
solo un metodo, i generatori hanno sia a __iter__
che a __next__
e un ulteriore close
.
Per la seconda domanda, la mia risposta personale sarebbe: in un'interfaccia pubblica, tendo a favorire molto i generatori, poiché è più resistente: il close
metodo e una maggiore componibilità conyield from
. A livello locale, posso usare gli iteratori, ma solo se è una struttura piatta e semplice (gli iteratori non si compongono facilmente) e se ci sono motivi per ritenere che la sequenza sia piuttosto breve, specialmente se può essere fermata prima che raggiunga la fine. Tendo a considerare gli iteratori come una primitiva di basso livello, tranne che per i letterali.
Per quanto riguarda il flusso di controllo, i generatori sono un concetto tanto importante quanto le promesse: entrambi sono astratti e compostabili.
__iter__
metodo, come mai un iteratore può avere __next__
solo? Se dovessero essere iterabili, mi aspetterei che lo facciano __iter__
anche loro.
__iter__
on iterables per restituire un iteratore, che richiede solo un next
metodo ( __next__
in Python3). Si prega di non confondere gli standard (per la tipizzazione anatra) con la loro implementazione (come lo ha implementato un particolare interprete Python). Questo è un po 'come la confusione tra le funzioni del generatore (definizione) e gli oggetti del generatore (implementazione). ;)
Funzione generatore, oggetto generatore, generatore:
Una funzione Generatore è proprio come una normale funzione in Python ma contiene una o più yield
istruzioni. Le funzioni del generatore sono un ottimo strumento per creare oggetti Iterator il più semplice possibile. L' oggetto Iterator ritorna nuovamente dalla funzione generatore è anche chiamato oggetto Generatore o Generatore .
In questo esempio ho creato una funzione Generator che restituisce un oggetto Generator <generator object fib at 0x01342480>
. Proprio come gli altri iteratori, gli oggetti Generatore possono essere utilizzati in un for
ciclo o con la funzione integrata next()
che restituisce il valore successivo dal generatore.
def fib(max):
a, b = 0, 1
for i in range(max):
yield a
a, b = b, a + b
print(fib(10)) #<generator object fib at 0x01342480>
for i in fib(10):
print(i) # 0 1 1 2 3 5 8 13 21 34
print(next(myfib)) #0
print(next(myfib)) #1
print(next(myfib)) #1
print(next(myfib)) #2
Quindi una funzione di generatore è il modo più semplice per creare un oggetto Iterator.
Iteratore :
Ogni oggetto generatore è un iteratore ma non viceversa. Un oggetto iteratore personalizzato può essere creato se la sua classe implementa __iter__
e __next__
metodo (chiamato anche protocollo iteratore).
Tuttavia, è molto più semplice utilizzare la funzione generatori per creare iteratori perché ne semplifica la creazione, ma un Iteratore personalizzato ti dà più libertà e puoi anche implementare altri metodi in base alle tue esigenze, come mostrato nell'esempio seguente.
class Fib:
def __init__(self,max):
self.current=0
self.next=1
self.max=max
self.count=0
def __iter__(self):
return self
def __next__(self):
if self.count>self.max:
raise StopIteration
else:
self.current,self.next=self.next,(self.current+self.next)
self.count+=1
return self.next-self.current
def __str__(self):
return "Generator object"
itobj=Fib(4)
print(itobj) #Generator object
for i in Fib(4):
print(i) #0 1 1 2
print(next(itobj)) #0
print(next(itobj)) #1
print(next(itobj)) #1
Esempi di Ned Batchelder altamente raccomandati per iteratori e generatori
Un metodo senza generatori che fa qualcosa per i numeri pari
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
mentre usando un generatore
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
return
dichiarazioneChiamare il evens
metodo (generatore) è come al solito
num = [...]
for n in evens(num):
do_smth(n)
Iterator
Un libro pieno di pagine è un iterabile , un segnalibro è un iteratore
e questo segnalibro non ha altro da fare se non quello di spostarsi next
litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration (Exception) as we got end of the iterator
Per usare Generator ... abbiamo bisogno di una funzione
Per usare Iterator ... abbiamo bisogno next
eiter
Come detto:
Una funzione Generatore restituisce un oggetto iteratore
L'intero vantaggio di Iterator:
Memorizza un elemento alla volta in memoria
È possibile confrontare entrambi gli approcci per gli stessi dati:
def myGeneratorList(n):
for i in range(n):
yield i
def myIterableList(n):
ll = n*[None]
for i in range(n):
ll[i] = i
return ll
# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
print("{} {}".format(i1, i2))
# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
Inoltre, se si controlla l'impronta della memoria, il generatore occupa molta meno memoria in quanto non è necessario memorizzare tutti i valori contemporaneamente.
Sto scrivendo appositamente per i neofiti di Python in un modo molto semplice, anche se in fondo Python fa così tante cose.
Cominciamo con la base:
Prendi in considerazione un elenco,
l = [1,2,3]
Scriviamo una funzione equivalente:
def f():
return [1,2,3]
o / p di print(l): [1,2,3]
& o / p diprint(f()) : [1,2,3]
Facciamo la lista iterabile: nella lista python è sempre iterabile ciò significa che puoi applicare iteratore quando vuoi.
Appliciamo iteratore sulla lista:
iter_l = iter(l) # iterator applied explicitly
Rendiamo iterabile una funzione, ovvero scriviamo una funzione generatore equivalente.
In Python non appena si introduce la parola chiave yield
; diventa una funzione generatore e l'iteratore verrà applicato implicitamente.
Nota: ogni generatore è sempre iterabile con l'iteratore implicito applicato e qui l'iteratore implicito è il punto cruciale Quindi la funzione del generatore sarà:
def f():
yield 1
yield 2
yield 3
iter_f = f() # which is iter(f) as iterator is already applied implicitly
Quindi se hai osservato, non appena hai creato la funzione fa generatore, è già iter (f)
Adesso,
l è l'elenco, dopo aver applicato il metodo iteratore "iter" diventa, iter (l)
f è già iter (f), dopo aver applicato il metodo iteratore "iter" diventa, iter (iter (f)), che è di nuovo iter (f)
È un po 'che stai lanciando int in int (x) che è già int e rimarrà int (x).
Ad esempio o / p di:
print(type(iter(iter(l))))
è
<class 'list_iterator'>
Non dimenticare mai che questo è Python e non C o C ++
Quindi la conclusione dalla spiegazione sopra è:
lista l ~ = iter (l)
funzione generatore f == iter (f)