Python-Schlüsselwort yield: Was ist das und wie setzt du es ein?
Das Schlüsselwort yield in Python macht aus einer normalen Funktion einen Generator, der Werte bei Bedarf nacheinander liefert, statt sie alle auf einmal zu berechnen.
Python-Funktionen haben nicht immer ein return-Statement. Generatorfunktionen sind Funktionen, die statt return das Schlüsselwort yield enthalten.
Diese Funktionen erzeugen Generator-Iteratoren — Objekte, die einen Datenstrom repräsentieren. Die Elemente eines Iterators werden erst dann erzeugt und geliefert, wenn sie gebraucht werden. Diese Art der Auswertung wird häufig als Lazy Evaluation bezeichnet.
Beim Arbeiten mit großen Datensätzen bieten Generatoren eine speicherschonende Alternative zum Ablegen von Daten in Listen, Tupeln und anderen Datenstrukturen, die für jedes einzelne Element Speicher benötigen. Generatorfunktionen können außerdem unendliche Iteratoren erzeugen — etwas, das mit streng ausgewerteten Strukturen wie Listen und Tupeln nicht möglich ist.
Bevor wir loslegen, fassen wir kurz die Unterschiede zwischen Funktionen und Generatoren zusammen:
Merkmal
Funktion
Generator
Werteerzeugung
Gibt alle Werte auf einmal zurück
Liefert Werte nacheinander, bei Bedarf
Ausführung
Läuft vollständig durch und gibt dann zurück
Hält nach yield an und macht weiter, wenn der nächste Wert angefordert wird
Schlüsselwort
return
yield
Speichereinsatz
Potentiell hoch, speichert die gesamte Sequenz im Speicher
Gering, speichert nur aktuellen Wert und Zustand
Iteration
Mehrfaches Iterieren möglich, erfordert aber das Vorhalten der gesamten Sequenz
Für Einzelpass-Iteration ausgelegt, effizient bei großen oder unendlichen Sequenzen
Generatorfunktionen mit Pythons yield erstellen
Der Begriff Generator kann sich in Python auf einen Generator-Iterator oder auf eine Generatorfunktion beziehen. Das sind unterschiedliche, aber verwandte Objekte. In diesem Tutorial verwenden wir der Klarheit halber meist die vollständigen Bezeichnungen.
Schauen wir uns zuerst Generatorfunktionen an. Eine Generatorfunktion sieht einer normalen Funktion ähnlich, enthält aber das Schlüsselwort yield statt return.
Wenn ein Python-Programm eine Generatorfunktion aufruft, entsteht ein Generator-Iterator. Iteratoren liefern auf Anfrage jeweils einen Wert und pausieren ihre Ausführung, bis ein weiterer Wert benötigt wird. Ein Beispiel macht den Unterschied zwischen normalen Funktionen und Generatorfunktionen deutlich:
Eine normale Funktion verwenden
Definieren wir zunächst eine normale Funktion mit einem return-Statement. Sie akzeptiert eine Sequenz von Wörtern und einen Buchstaben und gibt eine Liste mit der Anzahl der Vorkommen des Buchstabens in jedem Wort zurück:
deffind_letter_occurrences(words, letter):
output =[]for word in words:
output.append(word.count(letter))return output
print(
find_letter_occurrences(["apple","banana","cherry"],"a"))Bereitgestellt von
Die Funktion gibt die Liste 1, 3 und 0 zurück: In apple kommt ein a vor, in banana dreimal und in cherry keines. Dieselbe Funktion lässt sich mit einer List Comprehension kürzer schreiben, statt eine leere Liste zu initialisieren und .append() zu verwenden:
deffind_letter_occurrences(words, letter):return[word.count(letter)for word in words]Bereitgestellt von
Diese normale Funktion gibt bei jedem Aufruf eine Liste mit allen Ergebnissen zurück. Ist die Wortliste jedoch groß, belastet das den Speicher, da das Programm eine neue Liste in derselben Größe wie die Ausgangsliste anlegt und speichert. Wird die Funktion wiederholt mit verschiedenen Eingaben genutzt oder arbeiten ähnliche Funktionen parallel auf denselben Daten, steigt der Speicherdruck schnell an.
Eine Generatorfunktion verwenden
Stattdessen kann eine Generatorfunktion zum Einsatz kommen:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print(output)Bereitgestellt von
<generator object find_letter_occurrences at 0x102935e00>Bereitgestellt von
Die Funktion enthält das yield-Schlüsselwort statt return. Beim Aufruf liefert diese Generatorfunktion ein Generatorobjekt, das wir output zuweisen. Dieses Objekt ist ein Iterator. Es enthält nicht bereits die Ergebnisse, also die Anzahlen pro Wort. Stattdessen erzeugt und liefert der Generator die Werte bei Bedarf. Holen wir den ersten Wert aus diesem Generator-Iterator:
Die eingebaute Funktion next() ist eine Möglichkeit, den nächsten Wert aus einem Iterator zu holen. Weitere Varianten sehen wir später.
Der Code in der Generatorfunktion läuft, bis die Zeile mit yield erreicht ist. In diesem Beispiel startet die for-Schleife ihre erste Iteration und holt das erste Element aus words. Die String-Methode .count() gibt eine Ganzzahl zurück — hier 1, da apple genau ein a enthält. Der Generator liefert diesen Wert, und next(output) gibt ihn zurück.
Der Generator output pausiert nun. Er hat also die erste Iteration der for-Schleife abgeschlossen und die Anzahl der a im ersten Wort ermittelt. Jetzt wartet er, bis er erneut gebraucht wird.
Wird next() erneut mit output aufgerufen, setzt der Generator genau an der Pausenstelle fort:
Der Generator macht an der yield-Zeile der ersten Iteration weiter. Da danach kein weiterer Code in der Schleife steht, springt er zum Schleifenanfang zurück und holt das zweite Element aus words. .count() liefert hier 3, und dieser Wert wird ausgegeben. Anschließend pausiert der Generator wieder.
Der dritte Aufruf von next() setzt die Ausführung erneut fort:
Die Ausführung setzt nach dem Ende der dritten Iteration fort. Da es in words keine weiteren Elemente gibt, wirft der Generator die Ausnahme StopIteration.
In den meisten Fällen greifst du nicht direkt mit next() auf Generator-Elemente zu, sondern über einen Iterationsprozess. Die Ausnahme StopIteration signalisiert dessen Ende. Mehr dazu im nächsten Abschnitt.
Python bietet noch einen anderen Weg, Generator-Iteratoren zu erzeugen, wenn sich ihre Logik in einem einzelnen Ausdruck darstellen lässt, wie im vorigen Beispiel. Den Generator-Iterator output kannst du auch per Generatorausdruck erzeugen:
words =["apple","banana","cherry"]
letter ="a"
output =(word.count(letter)for word in words)print(next(output))print(next(output))print(next(output))print(next(output))Bereitgestellt von
Der Ausdruck in Klammern, der output zugewiesen wird, ist ein Generatorausdruck und erzeugt einen Generator-Iterator mit demselben Verhalten wie die Generatorfunktion find_letter_occurrences().
Zum Abschluss dieses Abschnitts noch ein weiteres Beispiel, das zeigt, wie die Ausführung bei jedem benötigten Element pausiert und fortgesetzt wird:
defshow_status():print("Start")yieldprint("Middle")yieldprint("End")yield
status = show_status()next(status)Bereitgestellt von
Diese Generatorfunktion enthält keine Schleife, sondern drei Zeilen mit yield. Beim Aufruf von show_status() entsteht der Generator-Iterator status. Der erste Aufruf von next(status) startet die Ausführung: Es wird "Start" gedruckt und nach dem ersten yield pausiert. Da hinter yield kein Objekt steht, liefert der Generator None.
Die Ausgabe "Middle" erscheint erst beim zweiten next()-Aufruf:
Weitere Einsatzmöglichkeiten von Generatoren sehen wir im nächsten Abschnitt.
Mit Generator-Iteratoren arbeiten
Generatorfunktionen erzeugen Generator-Iteratoren, und Iteratoren sind iterierbar. Was heißt das? Jedes Mal, wenn du eine Generatorfunktion aufrufst, entsteht ein Iterator. Da Iteratoren iterierbar sind, kannst du sie in for-Schleifen und anderen Iterationsprozessen verwenden.
next() ist daher nicht die einzige Möglichkeit, auf Elemente eines Iterators zuzugreifen. In diesem Abschnitt lernst du weitere Arbeitsweisen mit Generatoren kennen.
Pythons Iterationsprotokoll mit Generatoren nutzen
Greifen wir eine Generatorfunktion aus dem vorherigen Abschnitt wieder auf:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)for value in output:print(value)Bereitgestellt von
Statt next() mehrfach aufzurufen, wird der Generator-Iterator output hier in einer for-Schleife verwendet. Iteratoren sind iterierbar, daher funktionieren sie in for-Schleifen. Die Schleife holt Werte, bis keine mehr vorhanden sind.
Anders als Datenstrukturen wie Listen und Tupel lässt sich ein Iterator nur einmal durchlaufen. Führst du dieselbe for-Schleife ein zweites Mal auf demselben Iterator aus, gibt es keine Ausgabe mehr:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print("First attempt:")for value in output:print(value)print("Second attempt:")for value in output:print(value)Bereitgestellt von
Die erste for-Schleife erschöpft den Iterator, danach kann er keine Werte mehr liefern. Wenn du den Generator danach erneut benötigst, musst du einen neuen Generator-Iterator aus der Generatorfunktion erstellen.
Mehrere Generator-Iteratoren können gleichzeitig im Programm existieren:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
first_output = find_letter_occurrences(words, letter)
second_output = find_letter_occurrences(words, letter)print("First value of first_output:")print(next(first_output))print("Values of second_output:")for value in second_output:print(value)print("Remaining values of first_output:")for value in first_output:print(value)Bereitgestellt von
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0Bereitgestellt von
Die Generatorfunktion find_letter_occurrences() erzeugt zwei Generator-Iteratoren: first_output und second_output. Beide beziehen sich auf dieselben Ausgangsdaten in words, schreiten aber unabhängig voneinander voran.
Im Beispiel wird zunächst mit next() der erste Wert aus first_output geholt. Der Iterator liefert 1 und pausiert. Anschließend durchläuft eine Schleife second_output. Da dieser Generator noch keinen Wert geliefert hat, iteriert die Schleife über alle seine Werte. Zum Schluss iteriert eine weitere for-Schleife über first_output. Dieser Iterator hat seinen ersten Wert bereits geliefert, daher gibt die Schleife nur die restlichen Werte aus.
Die for-Schleife ist nicht die einzige Möglichkeit, über Generatoren zu iterieren:
print(*find_letter_occurrences(words, letter))print(sorted(find_letter_occurrences(words, letter)))Bereitgestellt von
In diesen Beispielen wird die Generatorfunktion direkt im Ausdruck aufgerufen, ohne den Iterator einer Variablen zuzuweisen. Im ersten Fall wird der Iterator per Sternchen-Operator entpackt. Das nutzt dasselbe Iterationsprotokoll wie die for-Schleife.
Im zweiten Fall wird der Generator-Iterator an die eingebaute Funktion sorted() übergeben, die ein iterierbares Argument erwartet. Generatoren sind iterierbar und können überall dort eingesetzt werden, wo Python iteriert.
Unendliche Iteratoren erzeugen
Ein Generator liefert einen Wert und pausiert, bis der nächste benötigt wird. Jedes Mal, wenn ein Wert angefordert wird, läuft der Code in der Generatorfunktion bis zum nächsten yield. In allen bisherigen Beispielen gab es nur endlich viele yield-Stellen. Es ist jedoch möglich, mit einer while-Schleife einen Generator zu erstellen, der unendlich viele Werte liefert. Im folgenden Beispiel gibt der Generator zufällig eine Farbe aus der übergebenen Liste zurück:
import random
defget_color(colors):whileTrue:yield random.choice(colors)
output_colors = get_color(["red","green","blue"])print("First two colors:")print(next(output_colors))print(next(output_colors))print("Next 10 colors using a 'for' loop:")for _ inrange(10):print(next(output_colors))Bereitgestellt von
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
redBereitgestellt von
Die Generatorfunktion get_color() enthält ein yield innerhalb einer while-Schleife. So trifft der Code bei der Suche nach dem nächsten Wert immer wieder auf ein yield. Der Iterator output_colors liefert unendlich viele Farben, zufällig aus der Eingabeliste gewählt. Dieser Generator wird nie erschöpft.
Unendliche Datenstrukturen wie Listen oder Tupel lassen sich nicht erzeugen. Generatoren ermöglichen hingegen unendliche Iterierbare. Beachte: Verwendest du den Generator direkt in einer for-Schleife, läuft die Schleife ohne Ende.
Fortgeschrittene Generator-Konzepte
Generatoren haben in Python weitere fortgeschrittene Anwendungsfälle. Einige davon sehen wir jetzt.
Objekte in den Generator senden
Generatoren können zusätzliche Daten entgegennehmen, die während der Auswertung genutzt werden. Die Anweisung mit dem Schlüsselwort yield ist ein Ausdruck, der zu einem Wert ausgewertet wird. Dieser Wert kann innerhalb der Generatorfunktion einer Variablen zugewiesen werden. Ein einfaches Beispiel:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(next(output))print(next(output))Bereitgestellt von
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
...
StopIterationBereitgestellt von
Das Python-Schlüsselwort yield bildet einen Ausdruck, der zu einem Wert ausgewertet wird. Dieser Wert innerhalb der Generatorfunktion ist jedoch nicht dasselbe Objekt, das der Generator nach außen liefert. Betrachte den ersten yield-Ausdruck: Der Generator liefert die Ganzzahl 1. Entsprechend zeigt print(next(output)) beim ersten Aufruf 1 an und pausiert die Ausführung des Generators.
Der yield-Ausdruck wird innerhalb des Generators aber ebenfalls zu einem Objekt ausgewertet, das hier der Variablen value zugewiesen wird. In diesem Beispiel wird None zugewiesen. Dasselbe passiert beim zweiten yield. Der dritte next()-Aufruf sorgt dafür, dass die restliche Funktion vollständig ausgeführt wird.
Ersetzen wir den zweiten und dritten next()-Aufruf durch .send(), eine Methode der Generator-Klasse:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(output.send("Here's a value"))print(output.send("Here's another value"))Bereitgestellt von
1
The yield expression evaluates to: Here's a value
2
The yield expression evaluates to: Here's another value
Traceback (most recent call last):
...
StopIterationBereitgestellt von
Die Generatorfunktion bleibt unverändert. Der Generator wird mit next() gestartet und liefert zuerst 1. Statt erneut next() aufzurufen, sendet das Programm mit output.send() ein Objekt (hier einen String) in den Generator. Der yield-Ausdruck im Generator wertet diesen String aus und weist ihn value zu. So kann der Generator mit dem gesendeten Wert weiterarbeiten.
Der zweite .send()-Aufruf sendet ein neues Objekt, das erneut value zugewiesen wird. Nach dem letzten print() folgt eine StopIteration, weil keine weiteren yield-Ausdrücke mehr vorhanden sind.
Noch ein Beispiel für .send(): Der folgende Generator zeigt den Kontostand an, der sich jedoch aktualisieren lässt:
defget_balance(start_balance):
balance = start_balance
whileTrue:
amount =yield balance
if amount isnotNone:
balance += amount
current_balance = get_balance(100)print(next(current_balance))print(current_balance.send(10))print(current_balance.send(-20))print(next(current_balance))Bereitgestellt von
Die Generatorfunktion erwartet beim Aufruf einen Startwert. Der Wert von balance kann sich während der Ausführung ändern. Jedes per .send() gesendete Objekt wird amount zugewiesen. Diese Variable ist entweder None (wenn der Generator ohne gesendeten Wert liefert, z. B. durch next()) oder enthält das per .send() übergebene Objekt.
Der Iterator current_balance startet mit einem Kontostand von 100. Mit next() wird der Generator gestartet und liefert den ersten Wert.
Sobald der Generator läuft, kannst du statt next() auch .send() verwenden. Der Generator addiert den gesendeten Wert zum Kontostand. Wenn kein Wert gesendet wird (z. B. durch erneutes next()), liefert der Generator den unveränderten Kontostand.
Direkt aus einem anderen Iterierbaren liefern
Python-Generatoren können mit der Syntax yield from direkt Werte aus einem anderen Generator oder Iterierbaren liefern. Beispiel für eine Generatorfunktion, die eine geschachtelte Liste abflacht:
defflatten(nested_list):for item in nested_list:ifisinstance(item,list):yieldfrom flatten(item)else:yield item
nested_list =[1,[2,3],[4,[5,6]],7]print(list(flatten(nested_list)))Bereitgestellt von
Die Generatorfunktion akzeptiert eine Liste, die weitere Listen enthalten kann. Die for-Schleife iteriert über die Elemente. Jedes Element ist entweder ein Wert (hier: eine Ganzzahl) oder eine weitere Liste. Ist es keine Liste, liefert der Generator das Element.
Handelt es sich um eine Liste, ruft der Generator rekursiv flatten() mit der inneren Liste auf. Dadurch entsteht ein weiterer Generator-Iterator, der die innere Liste als Datenquelle nutzt. Würden wir hier yield verwenden, würde der äußere Generator den inneren Generator selbst liefern. Mit yield from liefert der äußere Generator hingegen direkt die Werte des inneren Generators.
Zusammenfassung: yield vs. return
Funktionsdefinitionen mit return und yield wirken ähnlich, verhalten sich aber unterschiedlich. Die wichtigsten Unterschiede im Überblick:
Normale Funktion
Generatorfunktion
Schlüsselwort
return (implizit, wenn nicht explizit genutzt)
yield
Aufruf
Führt den Code bis zum return aus und gibt dann den Endwert zurück
Erzeugt einen Generator-Iterator
Beendigung
Wird durch das return-Statement beendet
Wird durch yield pausiert und kann später fortgesetzt werden
Rückgabewert
Ein einzelnes Objekt (kann eine Datenstruktur sein)
Generator-Iterator
Yield-Ausdruck
Nicht anwendbar (ergibt eine Anweisung)
Ergibt None oder den per .send() gesendeten Wert
Einsatzszenarien
Ideal, um ein finales Ergebnis zurückzugeben
Ideal, um Datenströme zu erzeugen — besonders für große oder unendliche Sequenzen
Fazit
Pythons yield-Schlüsselwort wird in Funktionen verwendet, um eine Generatorfunktion zu definieren. Beim Aufruf erzeugen diese Funktionen Generator-Iteratoren. Generatoren sind ein Beispiel für Lazy Evaluation in Python: Ausdrücke werden erst dann ausgewertet, wenn der Wert tatsächlich benötigt wird, nicht schon beim Ausführen des Ausdrucks. Der yield-Ausdruck eignet sich daher ideal, um Datenströme zu erzeugen, deren Werte bei Bedarf generiert werden — ohne sie im Speicher vorhalten zu müssen.
Gerade bei großen Datensätzen mit vielen Operationen zählt Effizienz. Generator-Iteratoren gehören zu den wichtigsten Werkzeugen, um große Datenmengen performant zu verarbeiten.
Ich habe Physik und Mathematik auf UG-Ebene an der Universität Malta studiert. Dann zog ich nach London und machte meinen Doktor in Physik am Imperial College. Ich habe an neuartigen optischen Techniken zur Abbildung der menschlichen Netzhaut gearbeitet. Jetzt konzentriere ich mich darauf, über Python zu schreiben, über Python zu kommunizieren und Python zu unterrichten.
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!
Quadrieren in Python ist einfach: Nutze den eingebauten Operator ** oder setze für vielseitigere Lösungen NumPy, pow(), math.pow(), Bitoperatoren und weitere Funktionen ein.
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.
Lerne Python für die Softwareentwicklung – vom Schreiben von Funktionen bis zum Definieren von Klassen. Erwirb die nötigen Fähigkeiten, um deine Karriere als Entwickler:in zu starten!
Python-Funktionen haben nicht immer ein
return-Statement. Generatorfunktionen sind Funktionen, die stattreturndas Schlüsselwortyieldenthalten.Diese Funktionen erzeugen Generator-Iteratoren — Objekte, die einen Datenstrom repräsentieren. Die Elemente eines Iterators werden erst dann erzeugt und geliefert, wenn sie gebraucht werden. Diese Art der Auswertung wird häufig als Lazy Evaluation bezeichnet.
Beim Arbeiten mit großen Datensätzen bieten Generatoren eine speicherschonende Alternative zum Ablegen von Daten in Listen, Tupeln und anderen Datenstrukturen, die für jedes einzelne Element Speicher benötigen. Generatorfunktionen können außerdem unendliche Iteratoren erzeugen — etwas, das mit streng ausgewerteten Strukturen wie Listen und Tupeln nicht möglich ist.
Bevor wir loslegen, fassen wir kurz die Unterschiede zwischen Funktionen und Generatoren zusammen:
Merkmal
Funktion
Generator
Werteerzeugung
Gibt alle Werte auf einmal zurück
Liefert Werte nacheinander, bei Bedarf
Ausführung
Läuft vollständig durch und gibt dann zurück
Hält nach yield an und macht weiter, wenn der nächste Wert angefordert wird
Schlüsselwort
return
yield
Speichereinsatz
Potentiell hoch, speichert die gesamte Sequenz im Speicher
Gering, speichert nur aktuellen Wert und Zustand
Iteration
Mehrfaches Iterieren möglich, erfordert aber das Vorhalten der gesamten Sequenz
Für Einzelpass-Iteration ausgelegt, effizient bei großen oder unendlichen Sequenzen
Generatorfunktionen mit Pythons
yielderstellenDer Begriff Generator kann sich in Python auf einen Generator-Iterator oder auf eine Generatorfunktion beziehen. Das sind unterschiedliche, aber verwandte Objekte. In diesem Tutorial verwenden wir der Klarheit halber meist die vollständigen Bezeichnungen.
Schauen wir uns zuerst Generatorfunktionen an. Eine Generatorfunktion sieht einer normalen Funktion ähnlich, enthält aber das Schlüsselwort
yieldstattreturn.Wenn ein Python-Programm eine Generatorfunktion aufruft, entsteht ein Generator-Iterator. Iteratoren liefern auf Anfrage jeweils einen Wert und pausieren ihre Ausführung, bis ein weiterer Wert benötigt wird. Ein Beispiel macht den Unterschied zwischen normalen Funktionen und Generatorfunktionen deutlich:
Eine normale Funktion verwenden
Definieren wir zunächst eine normale Funktion mit einem
return-Statement. Sie akzeptiert eine Sequenz von Wörtern und einen Buchstaben und gibt eine Liste mit der Anzahl der Vorkommen des Buchstabens in jedem Wort zurück:Die Funktion gibt die Liste 1, 3 und 0 zurück: In apple kommt ein a vor, in banana dreimal und in cherry keines. Dieselbe Funktion lässt sich mit einer List Comprehension kürzer schreiben, statt eine leere Liste zu initialisieren und
.append()zu verwenden:Diese normale Funktion gibt bei jedem Aufruf eine Liste mit allen Ergebnissen zurück. Ist die Wortliste jedoch groß, belastet das den Speicher, da das Programm eine neue Liste in derselben Größe wie die Ausgangsliste anlegt und speichert. Wird die Funktion wiederholt mit verschiedenen Eingaben genutzt oder arbeiten ähnliche Funktionen parallel auf denselben Daten, steigt der Speicherdruck schnell an.
Eine Generatorfunktion verwenden
Stattdessen kann eine Generatorfunktion zum Einsatz kommen:
Die Funktion enthält das
yield-Schlüsselwort stattreturn. Beim Aufruf liefert diese Generatorfunktion ein Generatorobjekt, das wiroutputzuweisen. Dieses Objekt ist ein Iterator. Es enthält nicht bereits die Ergebnisse, also die Anzahlen pro Wort. Stattdessen erzeugt und liefert der Generator die Werte bei Bedarf. Holen wir den ersten Wert aus diesem Generator-Iterator:Die eingebaute Funktion
next()ist eine Möglichkeit, den nächsten Wert aus einem Iterator zu holen. Weitere Varianten sehen wir später.Der Code in der Generatorfunktion läuft, bis die Zeile mit
yielderreicht ist. In diesem Beispiel startet diefor-Schleife ihre erste Iteration und holt das erste Element auswords. Die String-Methode.count()gibt eine Ganzzahl zurück — hier 1, da apple genau ein a enthält. Der Generator liefert diesen Wert, undnext(output)gibt ihn zurück.Der Generator
outputpausiert nun. Er hat also die erste Iteration derfor-Schleife abgeschlossen und die Anzahl der a im ersten Wort ermittelt. Jetzt wartet er, bis er erneut gebraucht wird.Wird
next()erneut mitoutputaufgerufen, setzt der Generator genau an der Pausenstelle fort:Der Generator macht an der
yield-Zeile der ersten Iteration weiter. Da danach kein weiterer Code in der Schleife steht, springt er zum Schleifenanfang zurück und holt das zweite Element auswords..count()liefert hier 3, und dieser Wert wird ausgegeben. Anschließend pausiert der Generator wieder.Der dritte Aufruf von
next()setzt die Ausführung erneut fort:Die zweite Iteration endet, die dritte beginnt. Wieder erreicht der Code die
yield-Zeile und liefert diesmal 0, da in cherry kein a vorkommt.Der Generator pausiert erneut. Erst beim vierten
next()-Aufruf entscheidet sich sein weiteres Schicksal:Die Ausführung setzt nach dem Ende der dritten Iteration fort. Da es in
wordskeine weiteren Elemente gibt, wirft der Generator die AusnahmeStopIteration.In den meisten Fällen greifst du nicht direkt mit
next()auf Generator-Elemente zu, sondern über einen Iterationsprozess. Die AusnahmeStopIterationsignalisiert dessen Ende. Mehr dazu im nächsten Abschnitt.Python bietet noch einen anderen Weg, Generator-Iteratoren zu erzeugen, wenn sich ihre Logik in einem einzelnen Ausdruck darstellen lässt, wie im vorigen Beispiel. Den Generator-Iterator
outputkannst du auch per Generatorausdruck erzeugen:Der Ausdruck in Klammern, der
outputzugewiesen wird, ist ein Generatorausdruck und erzeugt einen Generator-Iterator mit demselben Verhalten wie die Generatorfunktionfind_letter_occurrences().Zum Abschluss dieses Abschnitts noch ein weiteres Beispiel, das zeigt, wie die Ausführung bei jedem benötigten Element pausiert und fortgesetzt wird:
Diese Generatorfunktion enthält keine Schleife, sondern drei Zeilen mit
yield. Beim Aufruf vonshow_status()entsteht der Generator-Iteratorstatus. Der erste Aufruf vonnext(status)startet die Ausführung: Es wird"Start"gedruckt und nach dem erstenyieldpausiert. Da hinteryieldkein Objekt steht, liefert der GeneratorNone.Die Ausgabe
"Middle"erscheint erst beim zweitennext()-Aufruf:Nach dem zweiten
yieldpausiert der Generator erneut. Der dritte Aufruf vonnext()druckt schließlich"End":Der Generator hält am letzten
yieldan. Beim nächsten Abruf eines Werts von diesem Iterator wird eineStopIteration-Ausnahme ausgelöst:Weitere Einsatzmöglichkeiten von Generatoren sehen wir im nächsten Abschnitt.
Mit Generator-Iteratoren arbeiten
Generatorfunktionen erzeugen Generator-Iteratoren, und Iteratoren sind iterierbar. Was heißt das? Jedes Mal, wenn du eine Generatorfunktion aufrufst, entsteht ein Iterator. Da Iteratoren iterierbar sind, kannst du sie in
for-Schleifen und anderen Iterationsprozessen verwenden.next()ist daher nicht die einzige Möglichkeit, auf Elemente eines Iterators zuzugreifen. In diesem Abschnitt lernst du weitere Arbeitsweisen mit Generatoren kennen.Pythons Iterationsprotokoll mit Generatoren nutzen
Greifen wir eine Generatorfunktion aus dem vorherigen Abschnitt wieder auf:
Statt
next()mehrfach aufzurufen, wird der Generator-Iteratoroutputhier in einerfor-Schleife verwendet. Iteratoren sind iterierbar, daher funktionieren sie infor-Schleifen. Die Schleife holt Werte, bis keine mehr vorhanden sind.Anders als Datenstrukturen wie Listen und Tupel lässt sich ein Iterator nur einmal durchlaufen. Führst du dieselbe
for-Schleife ein zweites Mal auf demselben Iterator aus, gibt es keine Ausgabe mehr:Die erste
for-Schleife erschöpft den Iterator, danach kann er keine Werte mehr liefern. Wenn du den Generator danach erneut benötigst, musst du einen neuen Generator-Iterator aus der Generatorfunktion erstellen.Mehrere Generator-Iteratoren können gleichzeitig im Programm existieren:
Die Generatorfunktion
find_letter_occurrences()erzeugt zwei Generator-Iteratoren:first_outputundsecond_output. Beide beziehen sich auf dieselben Ausgangsdaten inwords, schreiten aber unabhängig voneinander voran.Im Beispiel wird zunächst mit
next()der erste Wert ausfirst_outputgeholt. Der Iterator liefert 1 und pausiert. Anschließend durchläuft eine Schleifesecond_output. Da dieser Generator noch keinen Wert geliefert hat, iteriert die Schleife über alle seine Werte. Zum Schluss iteriert eine weiterefor-Schleife überfirst_output. Dieser Iterator hat seinen ersten Wert bereits geliefert, daher gibt die Schleife nur die restlichen Werte aus.Die
for-Schleife ist nicht die einzige Möglichkeit, über Generatoren zu iterieren:In diesen Beispielen wird die Generatorfunktion direkt im Ausdruck aufgerufen, ohne den Iterator einer Variablen zuzuweisen. Im ersten Fall wird der Iterator per Sternchen-Operator entpackt. Das nutzt dasselbe Iterationsprotokoll wie die
for-Schleife.Im zweiten Fall wird der Generator-Iterator an die eingebaute Funktion
sorted()übergeben, die ein iterierbares Argument erwartet. Generatoren sind iterierbar und können überall dort eingesetzt werden, wo Python iteriert.Unendliche Iteratoren erzeugen
Ein Generator liefert einen Wert und pausiert, bis der nächste benötigt wird. Jedes Mal, wenn ein Wert angefordert wird, läuft der Code in der Generatorfunktion bis zum nächsten
yield. In allen bisherigen Beispielen gab es nur endlich vieleyield-Stellen. Es ist jedoch möglich, mit einerwhile-Schleife einen Generator zu erstellen, der unendlich viele Werte liefert. Im folgenden Beispiel gibt der Generator zufällig eine Farbe aus der übergebenen Liste zurück:Die Generatorfunktion
get_color()enthält einyieldinnerhalb einerwhile-Schleife. So trifft der Code bei der Suche nach dem nächsten Wert immer wieder auf einyield. Der Iteratoroutput_colorsliefert unendlich viele Farben, zufällig aus der Eingabeliste gewählt. Dieser Generator wird nie erschöpft.Unendliche Datenstrukturen wie Listen oder Tupel lassen sich nicht erzeugen. Generatoren ermöglichen hingegen unendliche Iterierbare. Beachte: Verwendest du den Generator direkt in einer
for-Schleife, läuft die Schleife ohne Ende.Fortgeschrittene Generator-Konzepte
Generatoren haben in Python weitere fortgeschrittene Anwendungsfälle. Einige davon sehen wir jetzt.
Objekte in den Generator senden
Generatoren können zusätzliche Daten entgegennehmen, die während der Auswertung genutzt werden. Die Anweisung mit dem Schlüsselwort
yieldist ein Ausdruck, der zu einem Wert ausgewertet wird. Dieser Wert kann innerhalb der Generatorfunktion einer Variablen zugewiesen werden. Ein einfaches Beispiel:Das Python-Schlüsselwort
yieldbildet einen Ausdruck, der zu einem Wert ausgewertet wird. Dieser Wert innerhalb der Generatorfunktion ist jedoch nicht dasselbe Objekt, das der Generator nach außen liefert. Betrachte den erstenyield-Ausdruck: Der Generator liefert die Ganzzahl 1. Entsprechend zeigtprint(next(output))beim ersten Aufruf 1 an und pausiert die Ausführung des Generators.Der
yield-Ausdruck wird innerhalb des Generators aber ebenfalls zu einem Objekt ausgewertet, das hier der Variablenvaluezugewiesen wird. In diesem Beispiel wirdNonezugewiesen. Dasselbe passiert beim zweitenyield. Der drittenext()-Aufruf sorgt dafür, dass die restliche Funktion vollständig ausgeführt wird.Ersetzen wir den zweiten und dritten
next()-Aufruf durch.send(), eine Methode der Generator-Klasse:Die Generatorfunktion bleibt unverändert. Der Generator wird mit
next()gestartet und liefert zuerst 1. Statt erneutnext()aufzurufen, sendet das Programm mitoutput.send()ein Objekt (hier einen String) in den Generator. Deryield-Ausdruck im Generator wertet diesen String aus und weist ihnvaluezu. So kann der Generator mit dem gesendeten Wert weiterarbeiten.Der zweite
.send()-Aufruf sendet ein neues Objekt, das erneutvaluezugewiesen wird. Nach dem letztenprint()folgt eineStopIteration, weil keine weiterenyield-Ausdrücke mehr vorhanden sind.Noch ein Beispiel für
.send(): Der folgende Generator zeigt den Kontostand an, der sich jedoch aktualisieren lässt:Die Generatorfunktion erwartet beim Aufruf einen Startwert. Der Wert von
balancekann sich während der Ausführung ändern. Jedes per.send()gesendete Objekt wirdamountzugewiesen. Diese Variable ist entwederNone(wenn der Generator ohne gesendeten Wert liefert, z. B. durchnext()) oder enthält das per.send()übergebene Objekt.Der Iterator
current_balancestartet mit einem Kontostand von 100. Mitnext()wird der Generator gestartet und liefert den ersten Wert.Sobald der Generator läuft, kannst du statt
next()auch.send()verwenden. Der Generator addiert den gesendeten Wert zum Kontostand. Wenn kein Wert gesendet wird (z. B. durch erneutesnext()), liefert der Generator den unveränderten Kontostand.Direkt aus einem anderen Iterierbaren liefern
Python-Generatoren können mit der Syntax
yield fromdirekt Werte aus einem anderen Generator oder Iterierbaren liefern. Beispiel für eine Generatorfunktion, die eine geschachtelte Liste abflacht:Die Generatorfunktion akzeptiert eine Liste, die weitere Listen enthalten kann. Die
for-Schleife iteriert über die Elemente. Jedes Element ist entweder ein Wert (hier: eine Ganzzahl) oder eine weitere Liste. Ist es keine Liste, liefert der Generator das Element.Handelt es sich um eine Liste, ruft der Generator rekursiv
flatten()mit der inneren Liste auf. Dadurch entsteht ein weiterer Generator-Iterator, der die innere Liste als Datenquelle nutzt. Würden wir hieryieldverwenden, würde der äußere Generator den inneren Generator selbst liefern. Mityield fromliefert der äußere Generator hingegen direkt die Werte des inneren Generators.Zusammenfassung:
yieldvs.returnFunktionsdefinitionen mit
returnundyieldwirken ähnlich, verhalten sich aber unterschiedlich. Die wichtigsten Unterschiede im Überblick:Normale Funktion
Generatorfunktion
Schlüsselwort
return (implizit, wenn nicht explizit genutzt)
yield
Aufruf
Führt den Code bis zum return aus und gibt dann den Endwert zurück
Erzeugt einen Generator-Iterator
Beendigung
Wird durch das return-Statement beendet
Wird durch yield pausiert und kann später fortgesetzt werden
Rückgabewert
Ein einzelnes Objekt (kann eine Datenstruktur sein)
Generator-Iterator
Yield-Ausdruck
Nicht anwendbar (ergibt eine Anweisung)
Ergibt
Noneoder den per.send()gesendeten WertEinsatzszenarien
Ideal, um ein finales Ergebnis zurückzugeben
Ideal, um Datenströme zu erzeugen — besonders für große oder unendliche Sequenzen
Fazit
Pythons
yield-Schlüsselwort wird in Funktionen verwendet, um eine Generatorfunktion zu definieren. Beim Aufruf erzeugen diese Funktionen Generator-Iteratoren. Generatoren sind ein Beispiel für Lazy Evaluation in Python: Ausdrücke werden erst dann ausgewertet, wenn der Wert tatsächlich benötigt wird, nicht schon beim Ausführen des Ausdrucks. Deryield-Ausdruck eignet sich daher ideal, um Datenströme zu erzeugen, deren Werte bei Bedarf generiert werden — ohne sie im Speicher vorhalten zu müssen.Gerade bei großen Datensätzen mit vielen Operationen zählt Effizienz. Generator-Iteratoren gehören zu den wichtigsten Werkzeugen, um große Datenmengen performant zu verarbeiten.
Wenn du mehr über Python lernen willst, wirf einen Blick auf diesen Python Developer Career Track.