Iteratoren sind Objekte, über die iteriert werden kann. Sie sind ein grundlegendes Feature der Programmiersprache Python und stecken hinter Schleifen und Listenabfragen. Jedes Objekt, das einen Iterator bereitstellen kann, nennt man iterierbar (iterable).
Das Konstruieren eines Iterators bringt einigen Aufwand mit sich. So muss die Implementierung jedes Iterator-Objekts eine __iter__()- und eine __next__()-Methode enthalten. Zusätzlich muss der interne Zustand des Objekts nachverfolgt werden, und sobald keine weiteren Werte mehr geliefert werden können, ist eine StopIteration-Exception auszulösen. Diese Regeln nennt man das Iterator-Protokoll.
Einen eigenen Iterator zu schreiben, ist ein längerer Prozess und nicht immer nötig. Eine einfachere Alternative ist die Verwendung eines Generators. Generatoren sind eine besondere Art von Funktion, die mit dem Schlüsselwort yield einen Iterator zurückgeben, über den dann Wert für Wert iteriert werden kann.
Zu wissen, wann du besser einen Iterator implementierst und wann ein Generator sinnvoller ist, bringt dich als Python-Programmierer deutlich weiter. Im restlichen Tutorial stellen wir die Unterschiede der beiden Ansätze heraus, damit du in verschiedenen Situationen die passende Wahl triffst.
Glossar
Begriff
Definition
Iterable
Ein Python-Objekt, über das in einer Schleife iteriert werden kann. Beispiele sind Listen, Sets, Tupel, Dictionaries, Strings etc.
Iterator
Ein Objekt, über das iteriert werden kann. Iteratoren enthalten also eine abzählbare Anzahl von Werten.
Generator
Eine besondere Art von Funktion, die nicht einen einzelnen Wert zurückgibt, sondern ein Iterator-Objekt mit einer Folge von Werten.
Lazy Evaluation
Eine Auswertungsstrategie, bei der bestimmte Objekte erst erzeugt werden, wenn sie wirklich gebraucht werden. Entsprechend wird Lazy Evaluation in Entwicklerkreisen auch als „call-by-need“ bezeichnet.
Iterator-Protokoll
Eine Menge von Regeln, die in Python befolgt werden müssen, um einen Iterator zu definieren.
next()
Eine eingebaute Funktion, die das nächste Element eines Iterators zurückgibt.
iter()
Eine eingebaute Funktion, die ein Iterable in einen Iterator umwandelt.
yield()
Ein Python-Schlüsselwort ähnlich zu return, mit dem Unterschied, dass yield ein Generator-Objekt statt eines Werts zurückgibt.
Python-Iteratoren & -Iterables
Iterables sind Objekte, die ihre Elemente nacheinander bereitstellen können – über sie kann iteriert werden. Bekannte eingebaute Python-Datenstrukturen wie Listen, Tupel und Sets sind iterierbar. Auch Strings und Dictionaries zählen dazu: Ein String kann über seine Zeichen iteriert werden, und die Keys eines Dictionaries lassen sich durchlaufen. Als Faustregel gilt: Jedes Objekt, über das du in einer for-Schleife iterieren kannst, ist ein Iterable.
Python-Iterables mit Beispielen erkunden
Aus den Definitionen folgt: Alle Iteratoren sind auch iterierbar. Aber nicht jedes Iterable ist automatisch ein Iterator. Ein Iterable erzeugt erst dann einen Iterator, wenn tatsächlich über es iteriert wird.
Zur Veranschaulichung instanziieren wir eine Liste (ein Iterable) und erzeugen daraus mit der eingebauten Funktion iter() einen Iterator.
list_instance =[1,2,3,4]print(iter(list_instance))"""
<list_iterator object at 0x7fd946309e90>
"""Bereitgestellt von
Auch wenn die Liste selbst kein Iterator ist, wandelt der Aufruf von iter() sie in einen Iterator um und gibt das Iterator-Objekt zurück.
Um zu zeigen, dass nicht alle Iterables Iteratoren sind, erzeugen wir dieselbe Liste und versuchen, die Funktion next() aufzurufen, die das nächste Element eines Iterators zurückgibt.
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
"""Bereitgestellt von
Wie du oben siehst, führt der Aufruf von next() auf einer Liste zu einem TypeError – mehr dazu im Tutorial Exception- und Fehlerbehandlung in Python. Der Grund ist schlicht, dass eine Liste ein Iterable ist, aber kein Iterator.
Python-Iteratoren mit Beispielen erkunden
Wenn du also explizit über eine Liste iterieren willst, musst du zuerst ein Iterator-Objekt erzeugen. Erst dann kannst du die Iteration über die Werte der Liste steuern.
# eine Liste instanziieren
list_instance =[1,2,3,4]# die Liste in einen Iterator umwandeln
iterator =iter(list_instance)# Elemente nacheinander abrufenprint(next(iterator))print(next(iterator))print(next(iterator))print(next(iterator))"""
1
2
3
4
"""Bereitgestellt von
Python erzeugt automatisch einen Iterator, sobald du versuchst, über ein iterierbares Objekt zu schleifen.
# eine Liste instanziieren
list_instance =[1,2,3,4]# über die Liste iterierenfor item in list_instance:print(item)"""
1
2
3
4
"""Bereitgestellt von
Wenn die Exception StopIteration ausgelöst wird, endet die Schleife.
Die Werte eines Iterators kannst du nur von links nach rechts abrufen. Es gibt in Python keine Funktion previous(), um im Iterator zurückzuspringen.
Die träge Natur von Iteratoren
Es ist möglich, mehrere Iteratoren auf Basis desselben Iterables zu erstellen. Jeder Iterator verwaltet dabei seinen eigenen Fortschritt. So kannst du eine Instanz bis zum Ende durchlaufen, während eine andere noch am Anfang steht.
Beachte, dass iterator_b das erste Element der Folge ausgibt.
Wir können also sagen: Iteratoren verhalten sich „lazy“. Beim Erstellen eines Iterators werden die Elemente nicht sofort geliefert, sondern erst, wenn sie abgefragt werden. Anders ausgedrückt: Die Elemente unserer Liste werden erst zurückgegeben, wenn wir sie explizit anfordern, z. B. mit next(iter(list_instance)).
Alle Werte eines Iterators lassen sich aber auch auf einmal herausziehen, indem du eine eingebaute Container-Datenstruktur (z. B. list(), set(), tuple()) auf das Iterator-Objekt anwendest und so die Erzeugung aller Elemente erzwingst.
# Iterable instanziieren
list_instance =[1,2,3,4]# aus dem Iterable einen Iterator erzeugen
iterator =iter(list_instance)print(list(iterator))"""
[1, 2, 3, 4]
"""Bereitgestellt von
Für sehr große Iteratoren ist das nicht zu empfehlen, da so alle Elemente auf einmal erzeugt und im Speicher gehalten werden – damit geht der Vorteil der Lazy Evaluation verloren.
Wenn ein Datensatz zu groß ist, um bequem in den Speicher zu passen, oder wenn du eine träge Iteration möchtest, ohne eine komplette Iterator-Klasse zu schreiben, ist ein Generator in der Regel die bessere Wahl.
Python-Generatoren
Die schnellste Alternative zur Implementierung eines Iterators ist ein Generator. Generatoren sehen zwar wie gewöhnliche Python-Funktionen aus, funktionieren aber anders. Ein Generator gibt Elemente nicht einfach zurück, sondern erzeugt sie mit dem Schlüsselwort yield bei Bedarf. Ein Generator ist also eine besondere Funktion, die Lazy Evaluation nutzt.
Generatoren speichern ihre Inhalte nicht im Speicher, wie es bei typischen Iterables der Fall ist. Möchten wir zum Beispiel alle Teiler einer positiven ganzen Zahl finden, würden wir klassisch eine normale Funktion implementieren (mehr zu Python-Funktionen in diesem Tutorial):
deffactors(n):
factor_list =[]for val inrange(1, n+1):if n % val ==0:
factor_list.append(val)return factor_list
print(factors(20))"""
[1, 2, 4, 5, 10, 20]
"""Bereitgestellt von
Der Code oben gibt die vollständige Liste der Teiler zurück. Mit einem Generator sieht das jedoch anders aus:
deffactors(n):for val inrange(1, n+1):if n % val ==0:yield val
print(factors(20))"""
<generator object factors at 0x7fd938271350>
"""Bereitgestellt von
Da wir yield statt return verwenden, wird die Funktion nicht beendet. Effektiv haben wir Python angewiesen, ein Generator-Objekt statt einer normalen Funktion zu erzeugen, dessen Zustand nachverfolgt werden kann.
Entsprechend können wir next() auf dem „trägen“ Iterator aufrufen und die Elemente der Folge einzeln ausgeben.
deffactors(n):for val inrange(1, n+1):if n % val ==0:yield val
factors_of_20 = factors(20)print(next(factors_of_20))"""
1
"""Bereitgestellt von
Eine weitere Möglichkeit, einen Generator zu erstellen, ist eine Generator-Comprehension. Sie nutzt eine ähnliche Syntax wie eine List Comprehension, allerdings mit runden statt eckigen Klammern.
factor_gen =(val for val inrange(1,21)if20% val ==0)print(list(factor_gen))"""
[1, 2, 4, 5, 10, 20]
"""Bereitgestellt von
Das yield-Schlüsselwort in Python
Das Schlüsselwort yield steuert den Ablauf einer Generatorfunktion. Anders als bei return wird die Funktion nicht verlassen, sondern pausiert. Dabei werden die lokalen Variablen und ihr Zustand gemerkt.
Der von yield zurückgegebene Generator kann einer Variablen zugewiesen und mit next() durchlaufen werden – dabei wird die Funktion bis zum ersten auftretenden yield ausgeführt. Sobald yield erreicht ist, wird die Ausführung angehalten und der Zustand gespeichert. So kannst du die Ausführung später nahtlos fortsetzen.
Die Funktion setzt ihre Ausführung direkt nach dem letzten yield fort. Zum Beispiel:
defyield_multiple_statements():yield"This is the first statement"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_statements()print(next(example))print(next(example))print(next(example))print(next(example))print(next(example))"""
This is the first statement
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:
"""Bereitgestellt von
In diesem Beispiel enthält unser Generator vier yield-Anweisungen, wir rufen jedoch fünfmal next auf. Dadurch wird eine StopIteration-Exception ausgelöst. Das passiert, weil unser Generator keine unendliche Folge liefert und durch zu viele Aufrufe erschöpft ist.
Fazit
Zusammengefasst: Iteratoren sind Objekte, über die iteriert werden kann, Generatoren sind spezielle Funktionen, die Lazy Evaluation nutzen. Implementierst du einen eigenen Iterator, musst du __iter__() und __next__() bereitstellen. Einen Generator implementierst du dagegen mit dem Schlüsselwort yield in einer Python-Funktion oder als Comprehension.
Einen eigenen Iterator bevorzugst du, wenn du ein Objekt mit komplexer Zustandslogik brauchst oder zusätzliche Methoden jenseits von __next__(), __iter__() und __init__() anbieten willst. Ein Generator ist oft die bessere Wahl bei großen Datenmengen, da keine Inhalte im Speicher gehalten werden, oder wenn es nicht nötig ist, einen Iterator von Grund auf zu implementieren.
What is the difference between an iterator and a generator in Python?⌄
Ein Iterator ist jedes Objekt, das __iter__() und __next__() implementiert. Ein Generator ist eine einfachere Möglichkeit, einen Iterator zu erstellen – über eine Funktion mit dem Schlüsselwort yield. Alle Generatoren sind Iteratoren, aber nicht alle Iteratoren sind Generatoren.
When should I use a generator instead of a list in Python?⌄
Verwende einen Generator für große oder unendliche Folgen oder wenn Speicherverbrauch wichtig ist. Listen halten alle Elemente gleichzeitig im Speicher, Generatoren liefern immer nur einen Wert nach dem anderen. Für kleine, wiederverwendete Datensätze ist eine Liste meist ausreichend.
What does the yield keyword do in Python?⌄
Das Schlüsselwort yield macht aus einer Funktion einen Generator. Statt zurückzukehren und zu beenden, pausiert yield die Funktion, gibt einen Wert zurück und merkt sich den Zustand, sodass die Ausführung beim nächsten Aufruf fortgesetzt werden kann.
How do you create a generator in Python?⌄
Entweder schreibst du eine Funktion, die yield statt return verwendet, oder du nutzt einen Generatorausdruck – gleiche Syntax wie eine List Comprehension, aber mit runden Klammern, z. B. (x * 2 for x in range(10)).
Are generators faster than iterators in Python?
⌄
Nicht in der reinen Ausführungsgeschwindigkeit, aber sie sind speichereffizienter, weil sie Werte bei Bedarf erzeugen. Bei großen Datenmengen führt das oft zu besserer Gesamtperformance; bei kleinen ist der Unterschied vernachlässigbar.
Ein umfassendes Einführungs-Tutorial zu Python-Schleifen. Lerne und übe while- und for-Schleifen, verschachtelte Schleifen, die Schlüsselwörter break und continue, die Range-Funktion und vieles mehr!
Lerne mehr über Python-Lambda-Funktionen, wozu sie gut sind und wann man sie benutzt. Enthält praktische Beispiele und bewährte Methoden für eine effektive Umsetzung.
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Iteratoren sind Objekte, über die iteriert werden kann. Sie sind ein grundlegendes Feature der Programmiersprache Python und stecken hinter Schleifen und Listenabfragen. Jedes Objekt, das einen Iterator bereitstellen kann, nennt man iterierbar (iterable).
Das Konstruieren eines Iterators bringt einigen Aufwand mit sich. So muss die Implementierung jedes Iterator-Objekts eine
__iter__()- und eine__next__()-Methode enthalten. Zusätzlich muss der interne Zustand des Objekts nachverfolgt werden, und sobald keine weiteren Werte mehr geliefert werden können, ist eineStopIteration-Exception auszulösen. Diese Regeln nennt man das Iterator-Protokoll.Einen eigenen Iterator zu schreiben, ist ein längerer Prozess und nicht immer nötig. Eine einfachere Alternative ist die Verwendung eines Generators. Generatoren sind eine besondere Art von Funktion, die mit dem Schlüsselwort
yieldeinen Iterator zurückgeben, über den dann Wert für Wert iteriert werden kann.Zu wissen, wann du besser einen Iterator implementierst und wann ein Generator sinnvoller ist, bringt dich als Python-Programmierer deutlich weiter. Im restlichen Tutorial stellen wir die Unterschiede der beiden Ansätze heraus, damit du in verschiedenen Situationen die passende Wahl triffst.
Glossar
Begriff
Definition
Iterable
Ein Python-Objekt, über das in einer Schleife iteriert werden kann. Beispiele sind Listen, Sets, Tupel, Dictionaries, Strings etc.
Iterator
Ein Objekt, über das iteriert werden kann. Iteratoren enthalten also eine abzählbare Anzahl von Werten.
Generator
Eine besondere Art von Funktion, die nicht einen einzelnen Wert zurückgibt, sondern ein Iterator-Objekt mit einer Folge von Werten.
Lazy Evaluation
Eine Auswertungsstrategie, bei der bestimmte Objekte erst erzeugt werden, wenn sie wirklich gebraucht werden. Entsprechend wird Lazy Evaluation in Entwicklerkreisen auch als „call-by-need“ bezeichnet.
Iterator-Protokoll
Eine Menge von Regeln, die in Python befolgt werden müssen, um einen Iterator zu definieren.
next()
Eine eingebaute Funktion, die das nächste Element eines Iterators zurückgibt.
iter()
Eine eingebaute Funktion, die ein Iterable in einen Iterator umwandelt.
yield()
Ein Python-Schlüsselwort ähnlich zu
return, mit dem Unterschied, dassyieldein Generator-Objekt statt eines Werts zurückgibt.Python-Iteratoren & -Iterables
Iterables sind Objekte, die ihre Elemente nacheinander bereitstellen können – über sie kann iteriert werden. Bekannte eingebaute Python-Datenstrukturen wie Listen, Tupel und Sets sind iterierbar. Auch Strings und Dictionaries zählen dazu: Ein String kann über seine Zeichen iteriert werden, und die Keys eines Dictionaries lassen sich durchlaufen. Als Faustregel gilt: Jedes Objekt, über das du in einer for-Schleife iterieren kannst, ist ein Iterable.
Python-Iterables mit Beispielen erkunden
Aus den Definitionen folgt: Alle Iteratoren sind auch iterierbar. Aber nicht jedes Iterable ist automatisch ein Iterator. Ein Iterable erzeugt erst dann einen Iterator, wenn tatsächlich über es iteriert wird.
Zur Veranschaulichung instanziieren wir eine Liste (ein Iterable) und erzeugen daraus mit der eingebauten Funktion
iter()einen Iterator.Auch wenn die Liste selbst kein Iterator ist, wandelt der Aufruf von
iter()sie in einen Iterator um und gibt das Iterator-Objekt zurück.Um zu zeigen, dass nicht alle Iterables Iteratoren sind, erzeugen wir dieselbe Liste und versuchen, die Funktion
next()aufzurufen, die das nächste Element eines Iterators zurückgibt.Wie du oben siehst, führt der Aufruf von
next()auf einer Liste zu einemTypeError– mehr dazu im Tutorial Exception- und Fehlerbehandlung in Python. Der Grund ist schlicht, dass eine Liste ein Iterable ist, aber kein Iterator.Python-Iteratoren mit Beispielen erkunden
Wenn du also explizit über eine Liste iterieren willst, musst du zuerst ein Iterator-Objekt erzeugen. Erst dann kannst du die Iteration über die Werte der Liste steuern.
Python erzeugt automatisch einen Iterator, sobald du versuchst, über ein iterierbares Objekt zu schleifen.
Wenn die Exception
StopIterationausgelöst wird, endet die Schleife.Die Werte eines Iterators kannst du nur von links nach rechts abrufen. Es gibt in Python keine Funktion
previous(), um im Iterator zurückzuspringen.Die träge Natur von Iteratoren
Es ist möglich, mehrere Iteratoren auf Basis desselben Iterables zu erstellen. Jeder Iterator verwaltet dabei seinen eigenen Fortschritt. So kannst du eine Instanz bis zum Ende durchlaufen, während eine andere noch am Anfang steht.
Beachte, dass
iterator_bdas erste Element der Folge ausgibt.Wir können also sagen: Iteratoren verhalten sich „lazy“. Beim Erstellen eines Iterators werden die Elemente nicht sofort geliefert, sondern erst, wenn sie abgefragt werden. Anders ausgedrückt: Die Elemente unserer Liste werden erst zurückgegeben, wenn wir sie explizit anfordern, z. B. mit
next(iter(list_instance)).Alle Werte eines Iterators lassen sich aber auch auf einmal herausziehen, indem du eine eingebaute Container-Datenstruktur (z. B.
list(),set(),tuple()) auf das Iterator-Objekt anwendest und so die Erzeugung aller Elemente erzwingst.Für sehr große Iteratoren ist das nicht zu empfehlen, da so alle Elemente auf einmal erzeugt und im Speicher gehalten werden – damit geht der Vorteil der Lazy Evaluation verloren.
Wenn ein Datensatz zu groß ist, um bequem in den Speicher zu passen, oder wenn du eine träge Iteration möchtest, ohne eine komplette Iterator-Klasse zu schreiben, ist ein Generator in der Regel die bessere Wahl.
Python-Generatoren
Die schnellste Alternative zur Implementierung eines Iterators ist ein Generator. Generatoren sehen zwar wie gewöhnliche Python-Funktionen aus, funktionieren aber anders. Ein Generator gibt Elemente nicht einfach zurück, sondern erzeugt sie mit dem Schlüsselwort
yieldbei Bedarf. Ein Generator ist also eine besondere Funktion, die Lazy Evaluation nutzt.Generatoren speichern ihre Inhalte nicht im Speicher, wie es bei typischen Iterables der Fall ist. Möchten wir zum Beispiel alle Teiler einer positiven ganzen Zahl finden, würden wir klassisch eine normale Funktion implementieren (mehr zu Python-Funktionen in diesem Tutorial):
Der Code oben gibt die vollständige Liste der Teiler zurück. Mit einem Generator sieht das jedoch anders aus:
Da wir
yieldstattreturnverwenden, wird die Funktion nicht beendet. Effektiv haben wir Python angewiesen, ein Generator-Objekt statt einer normalen Funktion zu erzeugen, dessen Zustand nachverfolgt werden kann.Entsprechend können wir
next()auf dem „trägen“ Iterator aufrufen und die Elemente der Folge einzeln ausgeben.Eine weitere Möglichkeit, einen Generator zu erstellen, ist eine Generator-Comprehension. Sie nutzt eine ähnliche Syntax wie eine List Comprehension, allerdings mit runden statt eckigen Klammern.
Das yield-Schlüsselwort in Python
Das Schlüsselwort
yieldsteuert den Ablauf einer Generatorfunktion. Anders als beireturnwird die Funktion nicht verlassen, sondern pausiert. Dabei werden die lokalen Variablen und ihr Zustand gemerkt.Der von
yieldzurückgegebene Generator kann einer Variablen zugewiesen und mitnext()durchlaufen werden – dabei wird die Funktion bis zum ersten auftretendenyieldausgeführt. Sobaldyielderreicht ist, wird die Ausführung angehalten und der Zustand gespeichert. So kannst du die Ausführung später nahtlos fortsetzen.Die Funktion setzt ihre Ausführung direkt nach dem letzten
yieldfort. Zum Beispiel:In diesem Beispiel enthält unser Generator vier
yield-Anweisungen, wir rufen jedoch fünfmalnextauf. Dadurch wird eineStopIteration-Exception ausgelöst. Das passiert, weil unser Generator keine unendliche Folge liefert und durch zu viele Aufrufe erschöpft ist.Fazit
Zusammengefasst: Iteratoren sind Objekte, über die iteriert werden kann, Generatoren sind spezielle Funktionen, die Lazy Evaluation nutzen. Implementierst du einen eigenen Iterator, musst du
__iter__()und__next__()bereitstellen. Einen Generator implementierst du dagegen mit dem Schlüsselwortyieldin einer Python-Funktion oder als Comprehension.Einen eigenen Iterator bevorzugst du, wenn du ein Objekt mit komplexer Zustandslogik brauchst oder zusätzliche Methoden jenseits von
__next__(),__iter__()und__init__()anbieten willst. Ein Generator ist oft die bessere Wahl bei großen Datenmengen, da keine Inhalte im Speicher gehalten werden, oder wenn es nicht nötig ist, einen Iterator von Grund auf zu implementieren.