Was ist der Unterschied zwischen Iteratoren und Generatoren? Einige Beispiele für die Verwendung der beiden Fälle wären hilfreich.
Zusammengefasst: Iteratoren sind Objekte, die eine __iter__
und eine __next__
( next
in Python 2) Methode. Generatoren bieten eine einfache, integrierte Möglichkeit, Instanzen von Iteratoren zu erstellen.
Eine Funktion mit Yield ist immer noch eine Funktion, die, wenn sie aufgerufen wird, eine Instanz eines Generatorobjekts zurückgibt:
def a_function():
"when called, returns generator object"
yield
Ein Generator-Ausdruck gibt auch einen Generator zurück:
a_generator = (i for i in range(0))
Für eine ausführlichere Darstellung und Beispiele lesen Sie bitte weiter.
Ein Generator es ein Iterator
Genauer gesagt ist generator ein Subtyp von iterator.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Wir können einen Generator auf verschiedene Arten erstellen. Eine sehr verbreitete und einfache Möglichkeit ist die Verwendung einer Funktion.
Genauer gesagt ist eine Funktion mit Yield eine Funktion, die bei ihrem 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 wiederum ein Iterator:
>>> isinstance(a_generator, collections.Iterator)
True
Ein Iterator es ein Iterables
Ein Iterator ist ein Iterable,
>>> issubclass(collections.Iterator, collections.Iterable)
True
die eine __iter__
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 eingebauten Tupel, Listen, Wörterbücher, Mengen, eingefrorene Mengen, Zeichenketten, Byte-Zeichenketten, Byte-Arrays, Bereiche und Speicheransichten:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
Iteratoren erfordern a next
o __next__
Methode
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
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 iter
Funktion:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
El __iter__
Methode wird aufgerufen, wenn Sie versuchen, ein Objekt mit einer for-Schleife zu verwenden. Dann wird die __next__
Methode wird für das Iterator-Objekt aufgerufen, um jedes Element für die Schleife herauszuholen. Der Iterator löst StopIteration
wenn Sie es aufgebraucht haben, und es kann dann nicht wieder verwendet werden.
Aus der Dokumentation
Aus dem Abschnitt Generator-Typen des Abschnitts Iterator-Typen des Abschnitts Eingebaute Typen Dokumentation :
Pythons Generatoren bieten eine bequeme Möglichkeit, das Iteratorprotokoll zu implementieren. Wenn ein Containerobjekt die __iter__()
Methode als Generator implementiert ist, gibt sie automatisch ein Iteratorobjekt (technisch gesehen ein Generatorobjekt) zurück, das die __iter__()
y next()
[ __next__()
in Python 3] Methoden. Weitere Informationen über Generatoren finden Sie in der Dokumentation zum Yield-Ausdruck.
(Hervorhebung hinzugefügt.)
Daraus lernen wir, dass Generatoren ein (praktischer) Typ von Iteratoren sind.
Beispiel-Iterator-Objekte
Sie können ein Objekt erstellen, das das Iterator-Protokoll implementiert, indem Sie Ihr 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
Aber es ist einfacher, dafür einen Generator zu verwenden:
def yes(stop):
for _ in range(stop):
yield 'yes'
Oder, vielleicht einfacher, ein Generator-Ausdruck (funktioniert ähnlich wie bei Listenauffassungen):
yes_expr = ('yes' for _ in range(stop))
Sie können alle auf dieselbe 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
Schlussfolgerung
Sie können das Iterator-Protokoll direkt verwenden, wenn Sie ein Python-Objekt zu einem Objekt erweitern müssen, über das iteriert werden kann.
In den allermeisten Fällen ist es jedoch am besten, wenn Sie yield
um eine Funktion zu definieren, die einen Generator-Iterator zurückgibt oder Generatorausdrücke berücksichtigt.
Schließlich ist zu beachten, dass Generatoren noch mehr Funktionen als Coroutines bieten. Ich erkläre Generatoren, zusammen mit dem yield
Erklärung, ausführlich auf meine Antwort auf die Frage "Was bewirkt das Schlüsselwort "yield"?".