Direkt zum Inhalt

Python Iteratoren und Generatoren Tutorial

Erforsche den Unterschied zwischen Python Iteratoren und Generatoren und lerne, welche in verschiedenen Situationen am besten zu verwenden sind.
Aktualisierte 16. Jan. 2025  · 10 Min. Lesezeit

Iteratoren sind Objekte, über die iteriert werden kann. Sie sind ein gemeinsames Merkmal der Programmiersprache Python, das für Schleifen und Listenauflösungen genutzt wird. Jedes Objekt, das einen Iterator ableiten kann, wird als Iterable bezeichnet. 

Es steckt viel Arbeit darin, einen Iterator zu konstruieren. Zum Beispiel muss die Implementierung jedes Iterator-Objekts aus einer __iter__() und __next__() Methode bestehen. Zusätzlich zu den oben genannten Voraussetzungen muss die Implementierung auch über eine Möglichkeit verfügen, den internen Zustand des Objekts zu verfolgen und eine StopIteration Ausnahme auszulösen, sobald keine Werte mehr zurückgegeben werden können. Diese Regeln sind als Iterator-Protokoll bekannt. 

Die Implementierung eines eigenen Iterators ist ein langwieriger Prozess, der nur manchmal notwendig ist. Eine einfachere Alternative ist die Verwendung eines Generatorobjekts. Generatoren sind eine besondere Art von Funktionen, die mit dem Schlüsselwort yield einen Iterator zurückgeben, über den ein Wert nach dem anderen iteriert werden kann. 

Die Fähigkeit, die richtigen Szenarien für die Implementierung eines Iterators oder die Verwendung eines Generators zu erkennen, wird deine Fähigkeiten als Python-Programmierer verbessern. Im weiteren Verlauf dieses Lernprogramms werden wir die Unterschiede zwischen den beiden Objekten hervorheben, um dir zu helfen, das beste Objekt für verschiedene Situationen zu wählen. 

Glossar

Begriff

Definition

Iterierbar 

Ein Python-Objekt, das in einer Schleife durchlaufen werden kann oder über das in einer Schleife iteriert wird. Beispiele für Iterables sind Listen, Mengen, Tupel, Wörterbücher, Strings usw. 

Iterator

Ein Iterator ist ein Objekt, über das iteriert werden kann. Iteratoren enthalten also eine abzählbare Anzahl von Werten. 

Generator

Eine besondere Art von Funktion, die keinen einzelnen Wert zurückgibt: Sie gibt ein Iterator-Objekt mit einer Folge von Werten zurück.

Faule Bewertung 

Eine Auswertungsstrategie, bei der bestimmte Objekte nur bei Bedarf produziert werden. In bestimmten Entwicklerkreisen spricht man deshalb auch von "lazy evaluation", also von "call-by-need".

Iterator-Protokoll 

Eine Reihe von Regeln, die befolgt werden müssen, um einen Iterator in Python zu definieren. 

next()

Eine eingebaute Funktion, die verwendet wird, um das nächste Element in einem Iterator zurückzugeben. 

iter()

Eine eingebaute Funktion, um eine Iterable in einen Iterator zu konvertieren. 

yield()

Ein Python-Schlüsselwort, das dem Schlüsselwort return ähnelt, mit dem Unterschied, dass yield ein Generator-Objekt statt eines Wertes zurückgibt. 

Python Iteratoren & Iterables

Iterables sind Objekte, die ihre Mitglieder einzeln zurückgeben können - man kann über sie iterieren. Beliebte eingebaute Python-Datenstrukturen wie Listen, Tupel und Sets gelten als Iterables. Andere Datenstrukturen wie Zeichenketten und Wörterbücher gelten ebenfalls als iterierbar: Eine Zeichenkette kann eine Iteration ihrer Zeichen erzeugen, und die Schlüssel eines Wörterbuchs können iteriert werden. Als Faustregel gilt: Jedes Objekt, über das in einer for-Schleife iteriert werden kann, ist eine Iterable. 

Python iterables mit Beispielen erforschen

Aus den Definitionen können wir schließen, dass alle Iteratoren auch iterierbar sind. Allerdings ist nicht jede Iterable unbedingt ein Iterator. Eine Iterable erzeugt nur dann einen Iterator, wenn auf ihr iteriert wird.

Um diese Funktionalität zu demonstrieren, werden wir eine Liste instanziieren, die eine Iterable ist, und einen Iterator erzeugen, indem wir die eingebaute Funktion iter() für die Liste aufrufen. 

list_instance = [1, 2, 3, 4]
print(iter(list_instance))

"""
<list_iterator object at 0x7fd946309e90>
"""

Obwohl die Liste an sich kein Iterator ist, wird sie durch den Aufruf der Funktion iter() in einen Iterator umgewandelt und das Iterator-Objekt zurückgegeben.

Um zu zeigen, dass nicht alle Iterables Iteratoren sind, werden wir dasselbe Listenobjekt instanziieren und versuchen, die Funktion next() aufzurufen, die verwendet wird, um das nächste Element in einem Iterator zurückzugeben.  

list_instance = [1, 2, 3, 4]
print(next(list_instance))
"""
--------------------------------------------------------------------
TypeError                         Traceback (most recent call last)
<ipython-input-2-0cb076ed2d65> in <module>()
    3 print(iter(list_instance))
    4
----> 5 print(next(list_instance))
TypeError: 'list' object is not an iterator
"""

Im obigen Code siehst du, dass der Versuch, die Funktion next() auf der Liste aufzurufen, eine TypeError ausgelöst hat - erfahre mehr über Ausnahmen und Fehlerbehandlung in Python. Dieses Verhalten trat aus dem einfachen Grund auf, dass ein Listenobjekt eine Iterable und kein Iterator ist. 

Python-Iteratoren mit Beispielen erforschen

Wenn das Ziel also ist, eine Liste zu iterieren, muss zuerst ein Iterator-Objekt erzeugt werden. Erst dann können wir die Iteration durch die Werte der Liste verwalten.

# instantiate a list object
list_instance = [1, 2, 3, 4]

# convert the list to an iterator
iterator = iter(list_instance)

# return items one at a time
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
"""
1
2
3
4
"""

Python erzeugt automatisch ein Iterator-Objekt, wenn du versuchst, eine Schleife durch ein iterables Objekt zu ziehen. 

# instantiate a list object
list_instance = [1, 2, 3, 4]

# loop through the list
for iterator in list_instance:
  print(iterator)
"""
1
2
3
4
"""

Wenn die Ausnahme StopIteration abgefangen wird, endet die Schleife.

Die von einem Iterator erhaltenen Werte können nur von links nach rechts abgerufen werden. In Python gibt es keine previous() Funktion, mit der du dich rückwärts durch einen Iterator bewegen kannst. 

Die faule Natur von Iteratoren

Es ist möglich, mehrere Iteratoren zu definieren, die auf demselben iterierbaren Objekt basieren. Jeder Iterator behält seinen eigenen Fortschrittsstatus bei. Wenn du also mehrere Iterator-Instanzen eines iterierbaren Objekts definierst, ist es möglich, bis zum Ende der einen Instanz zu iterieren, während die andere Instanz am Anfang bleibt.

list_instance = [1, 2, 3, 4]
iterator_a = iter(list_instance)
iterator_b = iter(list_instance)
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"B: {next(iterator_b)}")
"""
A: 1
A: 2
A: 3
A: 4
B: 1
"""

Beachte, dass iterator_b das erste Element der Reihe druckt.

Wir können also sagen, dass Iteratoren eine faule Natur haben: Wenn ein Iterator erstellt wird, werden die Elemente erst ausgegeben, wenn sie angefordert werden. Mit anderen Worten: Die Elemente unserer Listeninstanz werden nur zurückgegeben, wenn wir sie explizit mit next(iter(list_instance)) anfragen. 

Allerdings können alle Werte eines Iterators auf einmal extrahiert werden, indem ein eingebauter Container für iterierbare Datenstrukturen (z. B. list(), set(), tuple()) für das Iterator-Objekt aufgerufen wird, um den Iterator zu zwingen, alle seine Elemente auf einmal zu erzeugen.

# instantiate iterable
list_instance = [1, 2, 3, 4]

# produce an iterator from an iterable
iterator = iter(list_instance)
print(list(iterator))
"""
[1, 2, 3, 4]
"""

Es wird nicht empfohlen, diese Aktion durchzuführen, vor allem wenn die Elemente, die der Iterator zurückgibt, groß sind, da die Verarbeitung sehr lange dauert.

Wenn eine große Datendatei den Speicher deines Rechners überlastet oder du eine Funktion hast, deren interner Zustand bei jedem Aufruf aufrechterhalten werden muss, die Erstellung eines Iterators aber unter den gegebenen Umständen nicht sinnvoll ist, ist die Verwendung eines Generatorobjekts die bessere Alternative.

Python Generatoren

Die zweckmäßigste Alternative zur Implementierung eines Iterators ist die Verwendung eines Generators. Auch wenn Generatoren wie gewöhnliche Python-Funktionen aussehen, sind sie anders. Zunächst einmal gibt ein Generatorobjekt keine Gegenstände zurück. Stattdessen wird das Schlüsselwort yield verwendet, um Elemente im laufenden Betrieb zu erzeugen. Wir können also sagen, dass ein Generator eine besondere Art von Funktion ist, die eine faule Auswertung nutzt.

Generatoren speichern ihren Inhalt nicht im Speicher, wie du es von einer typischen Iterable erwarten würdest. Wenn es zum Beispiel darum geht, alle Faktoren einer positiven ganzen Zahl zu finden, würden wir eine herkömmliche Funktion (mehr über Python-Funktionen erfährst du in diesem Lernprogramm) wie folgt implementieren:  

def factors(n):
  factor_list = []
  for val in range(1, n+1):
      if n % val == 0:
          factor_list.append(val)
  return factor_list

print(factors(20))
"""
[1, 2, 4, 5, 10, 20]
"""

Der obige Code gibt die gesamte Liste der Faktoren zurück. Beachte jedoch den Unterschied, wenn ein Generator anstelle einer herkömmlichen Python-Funktion verwendet wird:

def factors(n):
  for val in range(1, n+1):
      if n % val == 0:
          yield val
print(factors(20))

"""
<generator object factors at 0x7fd938271350>
"""

Da wir das Schlüsselwort yield anstelle von return verwendet haben, wird die Funktion nach dem Lauf nicht beendet. Im Wesentlichen haben wir Python angewiesen, ein Generatorobjekt anstelle einer herkömmlichen Funktion zu erstellen, wodurch der Zustand des Generatorobjekts nachverfolgt werden kann. 

Daher ist es möglich, die Funktion next() für den Lazy Iterator aufzurufen, um die Elemente der Reihe nach anzuzeigen. 

def factors(n):
  for val in range(1, n+1):
      if n % val == 0:
          yield val
         
factors_of_20 = factors(20)
print(next(factors_of_20))

"""
1
"""

Eine andere Möglichkeit, einen Generator zu erstellen, ist ein Generatorverständnis. Generatorausdrücke haben eine ähnliche Syntax wie Listenausdrücke, nur dass sie abgerundete Klammern statt eckiger Klammern verwenden.

print((val for val in range(1, 20+1) if n % val == 0))
"""
<generator object <genexpr> at 0x7fd940c31e50>
"""

Pythons Erkundung yield Schlüsselwort

Das Schlüsselwort yield steuert den Ablauf einer Generatorfunktion. Anstatt die Funktion zu beenden, wie es bei der Verwendung von return der Fall ist, gibt das Schlüsselwort yield die Funktion zurück, merkt sich aber den Zustand der lokalen Variablen.

Der Generator, der beim Aufruf von yield zurückgegeben wird, kann einer Variablen zugewiesen und mit dem Schlüsselwort next() iteriert werden - dadurch wird die Funktion bis zum ersten yield Schlüsselwort ausgeführt, auf das sie trifft. Sobald das Schlüsselwort yield getroffen wird, wird die Ausführung der Funktion unterbrochen. Wenn dies geschieht, wird der Zustand der Funktion gespeichert. So ist es uns möglich, die Ausführung der Funktion nach eigenem Ermessen fortzusetzen. 

Die Funktion wird mit dem Aufruf von yield fortgesetzt. Zum Beispiel: 

def yield_multiple_statments():
  yield "This is the first statment"
  yield "This is the second statement"  
  yield "This is the third statement"
  yield "This is the last statement. Don't call next again!"
example = yield_multiple_statments()
print(next(example))
print(next(example))
print(next(example))
print(next(example))
print(next(example))
"""
This is the first statment
This is the second statement
This is the third statement
This is the last statement. Don't call next again or else!
--------------------------------------------------------------------
StopIteration                  Traceback (most recent call last)
<ipython-input-25-4aaf9c871f91> in <module>()
    11 print(next(example))
    12 print(next(example))
---> 13 print(next(example))
StopIteration:
"""

Im obigen Code hat unser Generator vier yield Aufrufe, aber wir versuchen, next fünfmal aufzurufen, was eine StopIteration Ausnahme auslöst. Dieses Verhalten trat auf, weil unser Generator keine unendliche Reihe ist und sich der Generator durch mehr Aufrufe als erwartet erschöpft hat.

Nachbereitung 

Iteratoren sind Objekte, auf denen iteriert werden kann, und Generatoren sind spezielle Funktionen, die eine faule Auswertung nutzen. Wenn du deinen eigenen Iterator implementierst, musst du eine Methode __iter__() und __next__() erstellen, während ein Generator mit dem Schlüsselwort yield in einer Python-Funktion oder einer Comprehension implementiert werden kann. 

Du kannst einen benutzerdefinierten Iterator einem Generator vorziehen, wenn du ein Objekt mit komplexem zustandserhaltendem Verhalten benötigst oder wenn du andere Methoden als __next__(), __iter__() und __init__() zur Verfügung stellen möchtest. Andererseits kann ein Generator vorzuziehen sein, wenn es um große Datenmengen geht, da sie ihren Inhalt nicht im Speicher ablegen oder wenn es nicht notwendig ist, einen Iterator zu implementieren. 

Themen

Top Python Kurse

Zertifizierung verfügbar

Kurs

Einführung in Funktionen in Python

3 hr
428.3K
Lerne die Kunst, deine eigenen Funktionen in Python zu schreiben, sowie Schlüsselkonzepte wie Scoping und Fehlerbehandlung.
Siehe DetailsRight Arrow
Kurs starten
Mehr anzeigenRight Arrow