Differenza tra generatori e iteratori di Python


Risposte:


544

iteratorè un concetto più generale: qualsiasi oggetto la cui classe ha un nextmetodo ( __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ù yieldespressioni ( yieldistruzioni, 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 Squarespotresti facilmente offrire metodi extra, ad es

    def current(self):
       return self.start

se hai effettivamente bisogno di tale funzionalità aggiuntiva nella tua applicazione.


come posso usare l'iteratore dopo averlo creato?
Vincenzooo,

@Vincenzooo dipende da cosa vuoi farci. O farà parte di un for ... in ...:, passato a una funzione, o chiameraiiter.next()
Caleth

@Caleth Stavo chiedendo la sintassi esatta, perché stavo ottenendo un errore nel tentativo di utilizzare la for..insintassi. Forse mi mancava qualcosa, ma è stato qualche tempo fa, non ricordo se ho risolto. Grazie!
Vincenzooo,

137

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__( nextin 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.

Un generatore è un iteratore

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

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

Gli iteratori richiedono un metodo nexto__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 iterfunzione:

>>> 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 StopIterationquando lo hai esaurito e non può essere riutilizzato a quel punto.

Dalla documentazione

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__()e next()[ __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.

Esempi di oggetti Iterator

È 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

Conclusione

È 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 yieldper 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 yielddichiarazione, in modo approfondito sulla mia risposta a "Che cosa fa la parola chiave" rendimento "?".


41

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 yieldmetodo.

Ogni next()chiamata di metodo sull'oggetto generatore (ad esempio: fcome 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
>>>

3
Solo la resa FYI non è un metodo, è una parola chiave
Jay Parikh,

25

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 utilizzandoyieldinvece 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à.


Non penso che ci sia molta confusione tra le funzioni del generatore e gli oggetti del generatore, per lo stesso motivo di solito non c'è confusione tra le classi e le loro istanze. In entrambi i casi, si chiama uno per ottenere l'altro e in una conversazione casuale (o documentazione scritta rapidamente) è possibile utilizzare il nome della classe o la parola "generatore" per uno di essi. Devi solo essere esplicito sulla "funzione generatore" rispetto a "oggetto generatore" in rare situazioni in cui quale di cui stai parlando conta.
Blckknght

6
1. Indipendentemente dalle ragioni teoriche del motivo per cui non dovrebbe esserci confusione, i commenti su altre risposte a questa domanda si negano e si contraddicono a vicenda senza soluzione, indicando che esiste un'effettiva confusione. 2. L'imprecisione casuale va bene, ma una fonte precisa e autorevole dovrebbe almeno essere una delle opzioni su SO. Uso ampiamente sia le funzioni del generatore che gli oggetti nel mio progetto attuale e la distinzione è molto importante durante la progettazione e la codifica. È utile sapere quale terminologia utilizzare ora, quindi non devo cambiare dozzine di nomi di variabili e commenti in seguito.
Paul

2
Immagina una letteratura matematica in cui non viene fatta alcuna distinzione tra una funzione e il suo valore di ritorno. Occasionalmente è conveniente confonderli in modo informale, ma aumenta il rischio di una varietà di errori. La matematica moderna avanzata sarebbe ostacolata in modo significativo e inutile se la distinzione non fosse formalizzata in termini di convenzione, linguaggio e notazione.
Paul

2
Le funzioni di ordine superiore che passano intorno ai generatori o alle funzioni del generatore possono sembrare strane, ma per me sono venute fuori. Sto lavorando in Apache Spark e applica uno stile di programmazione molto funzionale. Le funzioni devono creare, passare e distribuire tutti i tipi di oggetti per fare le cose. Ho avuto un certo numero di situazioni in cui ho perso la traccia del tipo di "generatore" con cui stavo lavorando. I suggerimenti nei nomi delle variabili e nei commenti, usando la terminologia coerente e corretta, hanno contribuito a chiarire la confusione. L'oscurità di un pitone può essere al centro del progetto di un altro!
Paul

1
@Paul, grazie per aver scritto questa risposta. Questa confusione è importante perché la differenza tra un oggetto generatore e una funzione generatore è la differenza tra ottenere il comportamento desiderato e dover cercare i generatori.
blujay

15

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 '.


10

Le risposte precedenti mancavano questa aggiunta: un generatore ha un closemetodo, mentre gli iteratori tipici no. Il closemetodo attiva StopIterationun'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 closemetodo 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.


Potresti fare un esempio per illustrare che intendi quando parli di composizione? Inoltre, puoi spiegare cosa hai in mente quando parli di " iteratori tipici "?
bli

1
Un'altra risposta ( stackoverflow.com/a/28353158/1878788 ) afferma che "un iteratore è un iterabile". Poiché un iterabile ha un __iter__metodo, come mai un iteratore può avere __next__solo? Se dovessero essere iterabili, mi aspetterei che lo facciano __iter__anche loro.
bli

1
@bli: AFAICS questa risposta qui si riferisce allo standard PEP234 , quindi è corretta, mentre l'altra risposta si riferisce ad alcune implementazioni, quindi è discutibile. Lo standard richiede solo un __iter__on iterables per restituire un iteratore, che richiede solo un nextmetodo ( __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). ;)
Tino,

7

Funzione generatore, oggetto generatore, generatore:

Una funzione Generatore è proprio come una normale funzione in Python ma contiene una o più yieldistruzioni. 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 forciclo 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

6

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
  • Non abbiamo bisogno di alcun elenco né di una returndichiarazione
  • Efficiente per flussi di lunghezza grande / infinita ... cammina e cede il valore

Chiamare il evensmetodo (generatore) è come al solito

num = [...]
for n in evens(num):
   do_smth(n)
  • Generatore utilizzato anche per rompere doppio loop

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 nexteiter

Come detto:

Una funzione Generatore restituisce un oggetto iteratore

L'intero vantaggio di Iterator:

Memorizza un elemento alla volta in memoria


A proposito del tuo primo frammento di codice, vorrei sapere cos'altro potrebbe essere arg "stream" oltre all'elenco []?
Iqra.

5

È 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.


1

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)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.