806 Stimmen

Unterschied zwischen Pythons Generatoren und Iteratoren

Was ist der Unterschied zwischen Iteratoren und Generatoren? Einige Beispiele für die Verwendung der beiden Fälle wären hilfreich.

825voto

Alex Martelli Punkte 805329

iterator ist ein allgemeineres Konzept: Jedes Objekt, dessen Klasse eine __next__ Methode ( next in Python 2) und ein __iter__ Methode, die die return self .

Jeder Generator ist ein Iterator, aber nicht umgekehrt. Ein Generator wird durch den Aufruf einer Funktion erzeugt, die eine oder mehrere yield Ausdrücke ( yield Anweisungen, in Python 2.5 und früher), und ist ein Objekt, das der Definition des vorherigen Absatzes eines iterator .

Sie können einen benutzerdefinierten Iterator anstelle eines Generators verwenden, wenn Sie eine Klasse mit etwas komplexem zustandserhaltendem Verhalten benötigen oder andere Methoden als __next__ (und __iter__ y __init__ ). Meistens wird ein Generator (manchmal, für ausreichend einfache Bedürfnisse, ein Generator Ausdruck ) ist ausreichend, und es ist einfacher zu kodieren, weil die Aufrechterhaltung des Zustands (innerhalb vernünftiger Grenzen) im Grunde "für Sie erledigt" ist, indem der Rahmen ausgesetzt und wieder aufgenommen wird.

Zum Beispiel, ein Generator wie:

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

oder der entsprechende Generatorausdruck (genexp)

generator = (i*i for i in range(a, b))

würde mehr Code benötigen, um einen eigenen Iterator zu erstellen:

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 2
       if self.start >= self.stop:
           raise StopIteration
       current = self.start * self.start
       self.start += 1
       return current

iterator = Squares(a, b)

Aber, natürlich, mit Klasse Squares Sie könnten problemlos zusätzliche Methoden anbieten, z. B.

    def current(self):
       return self.start

wenn Sie eine solche zusätzliche Funktionalität in Ihrer Anwendung tatsächlich benötigen.

1 Stimmen

Könnten Sie bitte klären, was hier der korrekte Fachjargon ist? Ich höre viele Leute den Begriff "Generator" austauschbar mit "Generatorfunktion" und "Generatorausdruck" verwenden, wie in einer Generatorfunktion est ein Generator und ein Generatorausdruck est einen Generator. Sie nennen ein "Generator-Objekt" den speziellen Typ von Iterator, den eine Generatorfunktion zurückgibt. Ich bin verwirrt.

3 Stimmen

Noch lustiger ist, dass beim Type Hinting ein Generator, der nur Werte liefert, als ein Iterator einfacher (d.h.: Generator[int, None, None] === Iterator[int] )

221voto

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"?".

57voto

Iteratoren sind Objekte, die die next() Methode, um die folgenden Werte einer Sequenz zu erhalten.

Stromerzeuger sind Funktionen, die eine Folge von Werten erzeugen oder liefern, indem sie die yield Stichwort.

Alle next() Methodenaufruf auf ein Generatorobjekt (z.B.: f unten), die von einer Generatorfunktion (z. B: foo() unten), erzeugt den nächsten Wert in der Folge.

Wenn eine Generatorfunktion aufgerufen wird, gibt sie ein Generatorobjekt zurück, ohne dass die Ausführung der Funktion überhaupt beginnt. Wenn die next() Methode zum ersten Mal aufgerufen wird, beginnt die Funktion mit ihrer Ausführung, bis sie ein yield Anweisung, die den ermittelten Wert zurückgibt. Die yield merkt sich, was geschehen ist, d.h. es merkt sich die letzte Ausführung. Und zweitens, die next() Der Aufruf wird mit dem vorherigen Wert fortgesetzt.

Das folgende Beispiel veranschaulicht das Zusammenspiel zwischen yield und der Aufruf der next Methode auf ein Generatorobjekt.

>>> def foo():
...     print("begin")
...     for i in range(3):
...         print("before yield", i)
...         yield i
...         print("after yield", i)
...     print("end")
...
>>> f = foo()
>>> next(f)
begin
before yield 0            # Control is in for loop
0
>>> next(f)
after yield 0             
before yield 1            # Continue for loop
1
>>> next(f)
after yield 1
before yield 2
2
>>> next(f)
after yield 2
end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

6 Stimmen

Nur zu Ihrer Information: Yield ist keine Methode, sondern ein Schlüsselwort.

2 Stimmen

Anstelle von f.next() sollte es sein next(f)

44voto

Paul Punkte 3139

Hinzufügen einer Antwort, da keine der vorhandenen Antworten speziell auf die Verwirrung in der offiziellen Literatur eingeht.

Generator-Funktionen sind gewöhnliche Funktionen, die mit yield anstelle von return . Wenn eine Generatorfunktion aufgerufen wird, gibt sie eine Generatorobjekt der eine Art Iterator ist - er hat eine next() Methode. Wenn Sie die next() wird der nächste Wert, den die Generatorfunktion liefert, zurückgegeben.

Je nachdem, welches Python-Quelldokument Sie lesen, wird entweder die Funktion oder das Objekt als "Generator" bezeichnet. Die Python-Glossar sagt Generatorfunktionen, während die Python-Wiki impliziert Generatorobjekte. Die Python-Tutorial schafft es bemerkenswerterweise, zu implizieren beide Verwendungen innerhalb von drei Sätzen:

Generatoren sind ein einfaches und leistungsfähiges Werkzeug zur Erstellung von Iteratoren. Sie werden wie normale Funktionen geschrieben, verwenden aber die yield-Anweisung, wenn sie Daten zurückgeben wollen. Jedes Mal, wenn next() aufgerufen wird, macht der Generator dort weiter, wo er aufgehört hat (er merkt sich alle Datenwerte und welche Anweisung zuletzt ausgeführt wurde).

In den ersten beiden Sätzen werden Generatoren mit Generatorfunktionen identifiziert, während sie im dritten Satz mit Generatorobjekten identifiziert werden.

Trotz all dieser Verwirrung kann man sich über die Python-Sprachreferenz für das klare und endgültige Wort:

Der Ausdruck yield wird nur bei der Definition einer Generatorfunktion verwendet und kann nur im Körper einer Funktionsdefinition verwendet werden. Die Verwendung eines yield-Ausdrucks in einer Funktionsdefinition reicht aus, um diese Definition zu veranlassen, eine Generatorfunktion anstelle einer normalen Funktion zu erstellen.

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.

Also im formellen und präzisen Sprachgebrauch, "Generator" unqualifiziert bedeutet Generatorobjekt, nicht Generatorfunktion.

Die oben genannten Referenzen sind für Python 2, aber Python 3 Sprachreferenz sagt das Gleiche. Allerdings ist die Python 3-Glossar stellt fest, dass

Generator ... Bezieht sich in der Regel auf eine Generatorfunktion, kann sich aber in manchen Kontexten auch auf einen Generator-Iterator beziehen. In Fällen, in denen die beabsichtigte Bedeutung nicht klar ist, vermeidet die Verwendung der vollständigen Begriffe Zweideutigkeit.

0 Stimmen

Ich glaube nicht, dass es viel Verwirrung zwischen Generatorfunktionen und Generatorobjekten gibt, aus dem gleichen Grund, aus dem es normalerweise keine Verwirrung zwischen Klassen und ihren Instanzen gibt. In beiden Fällen ruft man das eine auf, um das andere zu erhalten, und in einer lockeren Unterhaltung (oder einer schnell geschriebenen Dokumentation) kann man den Klassennamen oder das Wort "Generator" für beides verwenden. Nur in den seltenen Fällen, in denen es darauf ankommt, von welcher Klasse man spricht, muss man explizit zwischen "Generatorfunktion" und "Generatorobjekt" unterscheiden.

15 Stimmen

1. Unabhängig von den theoretischen Gründen, warum es keine Verwirrung geben sollte, leugnen die Kommentare zu anderen Antworten auf diese Frage einander und widersprechen einander ohne Auflösung, was darauf hindeutet, dass tatsächlich Verwirrung besteht. 2. Gelegentliche Ungenauigkeit ist in Ordnung, aber eine präzise, maßgebliche Quelle sollte zumindest eine der Optionen auf SO sein. In meinem aktuellen Projekt verwende ich sowohl Generatorfunktionen als auch Objekte in großem Umfang, und die Unterscheidung ist bei der Entwicklung und Programmierung sehr wichtig. Es ist gut zu wissen, welche Terminologie ich jetzt verwenden soll, damit ich später nicht Dutzende von Variablennamen und Kommentaren ändern muss.

6 Stimmen

Stellen Sie sich eine mathematische Literatur vor, in der nicht zwischen einer Funktion und ihrem Rückgabewert unterschieden wird. Gelegentlich ist es bequem, sie informell zu vermischen, aber es erhöht das Risiko einer Vielzahl von Fehlern. Die fortgeschrittene moderne Mathematik wäre erheblich und unnötig behindert, wenn die Unterscheidung nicht in Konventionen, Sprache und Notation formalisiert wäre.

36voto

Heapify Punkte 2221

Jeder hat eine wirklich schöne und ausführliche Antwort mit Beispielen gegeben und ich weiß das wirklich zu schätzen. Ich wollte nur eine kurze Antwort in wenigen Zeilen für diejenigen geben, denen das Konzept noch nicht ganz klar ist:

Wenn Sie Ihren eigenen Iterator erstellen, ist das ein wenig kompliziert - Sie müssen eine Klasse erstellen und zumindest die Methoden iter und next implementieren. Aber was ist, wenn Sie sich diese Mühe nicht machen wollen und schnell einen Iterator erstellen möchten? Glücklicherweise bietet Python eine Abkürzung für die Definition eines Iterators. Alles was Sie tun müssen, ist eine Funktion mit mindestens einem Aufruf von yield zu definieren, und wenn Sie diese Funktion aufrufen, gibt sie nun " etwas "die wie ein Iterator wirkt (Sie können die Methode next aufrufen und sie in einer for-Schleife verwenden). Diese etwas hat in Python einen Namen namens Generator

Ich hoffe, das klärt ein wenig auf.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X