Was ist der Unterschied zwischen Iteratoren und Generatoren? Einige Beispiele, wann Sie jeden Fall verwenden würden, wären hilfreich.
Was ist der Unterschied zwischen Iteratoren und Generatoren? Einige Beispiele, wann Sie jeden Fall verwenden würden, wären hilfreich.
Antworten:
iterator
ist ein allgemeineres Konzept: Jedes Objekt, dessen Klasse eine next
Methode ( __next__
in Python 3) und eine __iter__
Methode hat, die dies tut return self
.
Jeder Generator ist ein Iterator, aber nicht umgekehrt. Ein Generator wird durch Aufrufen einer Funktion erstellt, die einen oder mehrere yield
Ausdrücke enthält ( yield
Anweisungen in Python 2.5 und früheren Versionen) und ein Objekt ist, das der Definition von a im vorherigen Absatz entspricht iterator
.
Möglicherweise möchten Sie einen benutzerdefinierten Iterator anstelle eines Generators verwenden, wenn Sie eine Klasse mit einem etwas komplexen Verhalten zur Aufrechterhaltung des Zustands benötigen oder andere Methoden als next
(und __iter__
und __init__
) verfügbar machen möchten . Meistens ein Generator (manchmal für hinreichend einfache Bedürfnisse, ein Generator Ausdruck ) ist ausreichend, und es ist einfacher Code , weil Zustand Wartung (in Grenzen) ist im Grunde „für Sie getan“ durch den Rahmen aufgehängt zu werden und wieder aufgenommen.
Zum Beispiel ein Generator wie:
def squares(start, stop):
for i in range(start, stop):
yield i * i
generator = squares(a, b)
oder der äquivalente Generatorausdruck (genexp)
generator = (i*i for i in range(a, b))
würde mehr Code benötigen, um als benutzerdefinierter Iterator erstellt zu werden:
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)
Aber natürlich können Sie mit dem Unterricht Squares
leicht zusätzliche Methoden anbieten, z
def current(self):
return self.start
wenn Sie tatsächlich solche zusätzlichen Funktionen in Ihrer Anwendung benötigen.
for ... in ...:
einer Funktion sein, an eine Funktion übergeben, oder Sie rufen aniter.next()
for..in
Syntax zu verwenden, ein Fehler aufgetreten ist . Vielleicht hat mir etwas gefehlt, aber es ist einige Zeit her, ich erinnere mich nicht, ob ich es gelöst habe. Vielen Dank!
Was ist der Unterschied zwischen Iteratoren und Generatoren? Einige Beispiele, wann Sie jeden Fall verwenden würden, wären hilfreich.
Zusammenfassend: Iteratoren sind Objekte mit einem __iter__
und einem __next__
(next
in Python 2) Methode. Generatoren bieten eine einfache, integrierte Möglichkeit, Instanzen von Iteratoren zu erstellen.
Eine Funktion mit Yield darin ist immer noch eine Funktion, die beim Aufruf eine Instanz eines Generatorobjekts zurückgibt:
def a_function():
"when called, returns generator object"
yield
Ein Generatorausdruck gibt auch einen Generator zurück:
a_generator = (i for i in range(0))
Lesen Sie weiter, um eine ausführlichere Darstellung und Beispiele zu erhalten.
Insbesondere ist der Generator ein Subtyp des Iterators.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Wir können einen Generator auf verschiedene Arten erstellen. Ein sehr häufiger und einfacher Weg, dies zu tun, ist eine Funktion.
Insbesondere ist eine Funktion mit Ausbeute eine Funktion, die beim Aufruf einen Generator zurückgibt:
>>> 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'>
Und ein Generator ist wieder ein Iterator:
>>> isinstance(a_generator, collections.Iterator)
True
Ein Iterator ist ein Iterator,
>>> issubclass(collections.Iterator, collections.Iterable)
True
__iter__
Dies erfordert eine Methode, die einen Iterator zurückgibt:
>>> 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__
Einige Beispiele für Iterables sind die integrierten Tupel, Listen, Wörterbücher, Mengen, eingefrorenen Mengen, Zeichenfolgen, Byte-Zeichenfolgen, Byte-Arrays, Bereiche und Speicheransichten:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
next
oder__next__
MethodeIn 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
Und 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__
Wir können die Iteratoren von den eingebauten Objekten (oder benutzerdefinierten Objekten) mit der folgenden iter
Funktion abrufen:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
Die __iter__
Methode wird aufgerufen, wenn Sie versuchen, ein Objekt mit einer for-Schleife zu verwenden. Dann wird die __next__
Methode für das Iteratorobjekt aufgerufen, um jedes Element für die Schleife herauszuholen. Der Iterator wird ausgelöst, StopIteration
wenn Sie ihn erschöpft haben, und er kann zu diesem Zeitpunkt nicht wiederverwendet werden.
Aus dem Abschnitt "Generatortypen" des Abschnitts "Iteratortypen" der Dokumentation zu integrierten Typen :
Die Generatoren von Python bieten eine bequeme Möglichkeit, das Iteratorprotokoll zu implementieren. Wenn die
__iter__()
Methode eines Containerobjekts als Generator implementiert ist, gibt sie automatisch ein Iteratorobjekt (technisch gesehen ein Generatorobjekt) zurück, das die Methoden__iter__()
undnext()
[__next__()
in Python 3] bereitstellt. Weitere Informationen zu Generatoren finden Sie in der Dokumentation zum Ertragsausdruck.
(Betonung hinzugefügt.)
Daraus lernen wir, dass Generatoren eine (bequeme) Art von Iterator sind.
Sie können ein Objekt erstellen, das das Iterator-Protokoll implementiert, indem Sie ein eigenes Objekt erstellen oder erweitern.
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
Es ist jedoch einfacher, einfach einen Generator zu verwenden, um dies zu tun:
def yes(stop):
for _ in range(stop):
yield 'yes'
Oder vielleicht einfacher, ein Generatorausdruck (funktioniert ähnlich wie Listenverständnisse):
yes_expr = ('yes' for _ in range(stop))
Sie können alle auf die gleiche Weise verwendet werden:
>>> 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
Sie können das Iterator-Protokoll direkt verwenden, wenn Sie ein Python-Objekt als ein Objekt erweitern müssen, über das iteriert werden kann.
In den allermeisten Fällen können Sie jedoch am besten yield
eine Funktion definieren, die einen Generator-Iterator zurückgibt, oder Generator-Ausdrücke berücksichtigen.
Beachten Sie schließlich, dass Generatoren als Coroutinen noch mehr Funktionen bieten. Ich erkläre Generatoren zusammen mit der yield
Aussage ausführlich in meiner Antwort auf "Was macht das Schlüsselwort" Yield "?".
Iteratoren:
Iterator sind Objekte, die verwendet werden next()
Methode verwenden, um den nächsten Wert der Sequenz zu erhalten.
Generatoren:
Ein Generator ist eine Funktion, die mit eine Folge von Werten erzeugt oder liefert yield
Methode .
Jeder next()
Methodenaufruf für das Generatorobjekt (z. f
B. wie im folgenden Beispiel), der von der Generatorfunktion zurückgegeben wird (z. foo()
B. Funktion im folgenden Beispiel), generiert den nächsten Wert nacheinander.
Wenn eine Generatorfunktion aufgerufen wird, gibt sie ein Generatorobjekt zurück, ohne mit der Ausführung der Funktion zu beginnen. Wannnext()
Methode zum ersten Mal aufgerufen wird, wird die Funktion ausgeführt, bis sie die Yield-Anweisung erreicht, die den Yield-Wert zurückgibt. Die Ausbeute verfolgt, dh erinnert sich an die letzte Ausführung. Der zweite next()
Aufruf wird vom vorherigen Wert fortgesetzt.
Das folgende Beispiel zeigt das Zusammenspiel zwischen Ausbeute und Aufruf der nächsten Methode für das Generatorobjekt.
>>> 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
>>>
Hinzufügen einer Antwort, da keine der vorhandenen Antworten speziell die Verwirrung in der offiziellen Literatur anspricht.
Generatorfunktionen sind gewöhnliche Funktionen, die mityield
anstelle vondefiniert werdenreturn
. Beim Aufruf gibt eine Generatorfunktion ein Generatorobjekt zurück , das eine Art Iterator ist - es hat einenext()
Methode. Wenn du anrufstnext()
, wird der nächste von der Generatorfunktion ausgegebene Wert zurückgegeben.
Je nachdem, welches Python-Quelldokument Sie lesen, kann entweder die Funktion oder das Objekt als "Generator" bezeichnet werden. Das Python-Glossar besagt, dass Generatorfunktionen vorhanden sind, während das Python-Wiki Generatorobjekte impliziert. Das Python-Tutorial schafft es bemerkenswerterweise, beide Verwendungen innerhalb von drei Sätzen zu implizieren :
Generatoren sind ein einfaches und leistungsstarkes Werkzeug zum Erstellen von Iteratoren. Sie sind wie reguläre Funktionen geschrieben, verwenden jedoch die Yield-Anweisung, wenn sie Daten zurückgeben möchten. Bei jedem Aufruf von next () wird der Generator dort fortgesetzt, wo er aufgehört hat (er merkt sich alle Datenwerte und welche Anweisung zuletzt ausgeführt wurde).
Die ersten beiden Sätze identifizieren Generatoren mit Generatorfunktionen, während der dritte Satz sie mit Generatorobjekten identifiziert.
Trotz all dieser Verwirrung kann man die Python-Sprachreferenz für das klare und endgültige Wort suchen :
Der Ertragsausdruck wird nur beim Definieren einer Generatorfunktion verwendet und kann nur im Hauptteil einer Funktionsdefinition verwendet werden. Die Verwendung eines Ertragsausdrucks in einer Funktionsdefinition reicht aus, um zu bewirken, dass diese Definition eine Generatorfunktion anstelle einer normalen Funktion erstellt.
Wenn eine Generatorfunktion aufgerufen wird, gibt sie einen Iterator zurück, der als Generator bezeichnet wird. Dieser Generator steuert dann die Ausführung einer Generatorfunktion.
In der formalen und präzisen Verwendung bedeutet "Generator" unqualifiziert also Generatorobjekt, nicht Generatorfunktion.
Die obigen Referenzen gelten für Python 2, aber die Python 3-Sprachreferenz sagt dasselbe. Das Python 3-Glossar besagt dies jedoch
Generator ... Bezieht sich normalerweise auf eine Generatorfunktion, kann sich jedoch in einigen Kontexten auf einen Generatoriterator beziehen. In Fällen, in denen die beabsichtigte Bedeutung nicht klar ist, werden durch die Verwendung der vollständigen Begriffe Mehrdeutigkeiten vermieden.
Jeder hat eine wirklich nette und ausführliche Antwort mit Beispielen und ich schätze es sehr. Ich wollte nur ein paar kurze Antworten für Leute geben, die konzeptionell noch nicht ganz klar sind:
Wenn Sie Ihren eigenen Iterator erstellen, ist dies ein wenig kompliziert - Sie müssen eine Klasse erstellen und mindestens den Iter und die nächsten Methoden implementieren. Aber was ist, wenn Sie diesen Aufwand nicht durchlaufen und schnell einen Iterator erstellen möchten? Glücklicherweise bietet Python eine Abkürzung zum Definieren eines Iterators. Alles, was Sie tun müssen, ist, eine Funktion mit mindestens 1 Aufruf zu definieren. Wenn Sie diese Funktion aufrufen, gibt sie " etwas " zurück, das sich wie ein Iterator verhält (Sie können die nächste Methode aufrufen und in einer for-Schleife verwenden). Das etwas hat in Python einen Namen namens Generator
Hoffe das klärt ein bisschen.
In früheren Antworten wurde dieser Zusatz übersehen: Ein Generator verfügt über eine close
Methode, typische Iteratoren dagegen nicht. Die close
Methode löst StopIteration
im Generator eine Ausnahme aus, die in a abgefangen werden kannfinally
Klausel in diesem Iterator enthalten ist, um die Möglichkeit zu erhalten, eine Bereinigung auszuführen. Diese Abstraktion macht es am nützlichsten in großen als einfachen Iteratoren. Man kann einen Generator schließen, wie man eine Datei schließen könnte, ohne sich darum kümmern zu müssen, was darunter liegt.
Meine persönliche Antwort auf die erste Frage wäre jedoch: iteratable hat nur eine __iter__
Methode, typische Iteratoren haben nur eine __next__
Methode, Generatoren haben sowohl eine __iter__
als auch eine __next__
und eine zusätzlicheclose
.
Für die zweite Frage wäre meine persönliche Antwort: In einer öffentlichen Schnittstelle tendiere ich dazu, Generatoren sehr zu bevorzugen, da sie widerstandsfähiger sind: die close
Methode eine größere Kompositionsfähigkeit mityield from
. Vor Ort kann ich Iteratoren verwenden, aber nur, wenn es sich um eine flache und einfache Struktur handelt (Iteratoren lassen sich nicht leicht zusammensetzen) und wenn Grund zu der Annahme besteht, dass die Sequenz ziemlich kurz ist, insbesondere wenn sie vor dem Ende gestoppt werden kann. Ich neige dazu, Iteratoren als ein Grundelement niedriger Ebene zu betrachten, außer als Literale.
Generatoren sind für Kontrollflussfragen ein ebenso wichtiges Konzept wie Versprechen: Beide sind abstrakt und zusammensetzbar.
__iter__
Wie kommt es, dass ein Iterator __next__
nur eine Methode haben kann, da er eine Methode hat ? Wenn sie iterabel sein sollen, würde ich erwarten, dass sie notwendigerweise auch haben __iter__
.
__iter__
on iterables, um einen Iterator zurückzugeben, für den nur eine next
Methode erforderlich ist ( __next__
in Python3). Bitte verwechseln Sie Standards (für die Eingabe von Enten) nicht mit ihrer Implementierung (wie ein bestimmter Python-Interpreter sie implementiert hat). Dies ist ein bisschen wie die Verwechslung zwischen Generatorfunktionen (Definition) und Generatorobjekten (Implementierung). ;)
Generatorfunktion, Generatorobjekt, Generator:
Eine Generatorfunktion ist wie eine reguläre Funktion in Python, enthält jedoch eine oder mehrere yield
Anweisungen. Generatorfunktionen sind ein großartiges Werkzeug, um Iteratorobjekte so einfach wie möglich zu erstellen . Das durch die Generatorfunktion zurückgegebene Iteratorobjekt wird auch als Generatorobjekt oder Generator bezeichnet .
In diesem Beispiel habe ich eine Generatorfunktion erstellt, die ein Generatorobjekt zurückgibt <generator object fib at 0x01342480>
. Genau wie andere Iteratoren können Generatorobjekte in einer for
Schleife oder mit der integrierten Funktion verwendet werden, next()
die den nächsten Wert vom Generator zurückgibt.
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
Eine Generatorfunktion ist daher der einfachste Weg, ein Iterator-Objekt zu erstellen.
Iterator :
Jedes Generatorobjekt ist ein Iterator, aber nicht umgekehrt. Ein benutzerdefiniertes Iteratorobjekt kann erstellt werden, wenn seine Klasse eine Methode __iter__
und eine __next__
Methode implementiert (auch als Iteratorprotokoll bezeichnet).
Es ist jedoch viel einfacher, die Generatorfunktion zum Erstellen von Iteratoren zu verwenden, da diese ihre Erstellung vereinfachen. Ein benutzerdefinierter Iterator bietet Ihnen jedoch mehr Freiheit und Sie können auch andere Methoden gemäß Ihren Anforderungen implementieren, wie im folgenden Beispiel gezeigt.
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
Beispiele von Ned Batchelder werden für Iteratoren und Generatoren dringend empfohlen
Eine Methode ohne Generatoren, die gerade Zahlen macht
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
während mit einem Generator
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
return
ErklärungDas Aufrufen der evens
Methode (Generator) ist wie gewohnt
num = [...]
for n in evens(num):
do_smth(n)
Iterator
Ein Buch voller Seiten ist iterierbar . Ein Lesezeichen ist ein Iterator
und dieses Lesezeichen hat nichts zu tun, außer sich zu bewegen 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
Um Generator zu benutzen ... brauchen wir eine Funktion
Um Iterator zu benutzen ... brauchen wir next
unditer
Wie gesagt:
Eine Generatorfunktion gibt ein Iteratorobjekt zurück
Der ganze Nutzen von Iterator:
Speichern Sie jeweils ein Element im Speicher
Sie können beide Ansätze für dieselben Daten vergleichen:
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)))
Wenn Sie den Speicherbedarf überprüfen, benötigt der Generator außerdem viel weniger Speicher, da nicht alle Werte gleichzeitig im Speicher gespeichert werden müssen.
Ich schreibe auf sehr einfache Weise speziell für Python-Neulinge, obwohl Python im Grunde so viele Dinge tut.
Beginnen wir mit dem Grundlegenden:
Betrachten Sie eine Liste,
l = [1,2,3]
Schreiben wir eine äquivalente Funktion:
def f():
return [1,2,3]
o / p von print(l): [1,2,3]
& o / p vonprint(f()) : [1,2,3]
Lassen Sie uns die Liste iterierbar machen: In Python ist die Liste immer iterierbar, dh Sie können den Iterator jederzeit anwenden.
Wenden wir den Iterator auf die Liste an:
iter_l = iter(l) # iterator applied explicitly
Lassen Sie uns eine Funktion iterierbar machen, dh eine äquivalente Generatorfunktion schreiben.
In Python, sobald Sie das Schlüsselwort eingeben yield
; Es wird eine Generatorfunktion und der Iterator wird implizit angewendet.
Hinweis: Jeder Generator ist immer iterierbar, wenn der implizite Iterator angewendet wird. Hier ist der implizite Iterator der springende Punkt. Die Generatorfunktion lautet also:
def f():
yield 1
yield 2
yield 3
iter_f = f() # which is iter(f) as iterator is already applied implicitly
Wenn Sie also beobachtet haben, sobald Sie die Funktion eines Generators erstellt haben, ist dies bereits iter (f).
Jetzt,
l ist die Liste, nach Anwendung der Iteratormethode "iter" wird es iter (l)
f ist bereits iter (f), nach Anwendung der Iteratormethode "iter" wird es iter (iter (f)), was wiederum iter (f) ist.
Es ist irgendwie so, dass Sie int in int (x) umwandeln, das bereits int ist und int (x) bleibt.
Zum Beispiel o / p von:
print(type(iter(iter(l))))
ist
<class 'list_iterator'>
Vergiss niemals, dass dies Python ist und nicht C oder C ++
Daher lautet die Schlussfolgerung aus der obigen Erklärung:
Liste l ~ = iter (l)
Generatorfunktion f == iter (f)