Eine Funktion, die pausiert
Ein Generator sieht aus wie eine normale Funktion, aber statt ein ganzes Ergebnis zu berechnen und zurückzugeben, liefert er einen Wert nach dem anderen und pausiert zwischen den Yields, bis der Konsument den nächsten Wert will.
Der einfachste mögliche:
Beachte yield statt return. Das erste Mal, wenn for einen Wert will, läuft Python den Funktionsrumpf bis yield 1. Die Funktion pausiert genau dort, reicht 1 an die Schleife und merkt sich, wo sie angehalten hat — Variablen und alles. Die nächste Iteration setzt dort an: current += 1, zurück zum while, yield 2. Und so weiter, bis die Schleifenbedingung scheitert; dann hält der Generator einfach an.
Dieses Anhalten-und-Weitermachen ist der ganze Trick.
Warum nicht einfach eine Liste bauen?
Weil die Listenversion alle Werte im Voraus alloziert:
Okay für 5 Elemente. Jetzt stell dir vor, du willst 50 Millionen Ganzzahlen und interessierst dich nur für die erste, die eine Bedingung erfüllt. Die Listenversion alloziert 50 Millionen Ints und wirft die meisten weg. Die Generator-Version erzeugt genau so viele, wie die Aufruferin konsumiert. Findet die for-Schleife, was sie will, und bricht ab, hält der Generator einfach an.
Das Muster, das du verinnerlichen solltest: mit Generatoren schreibst du Iterationscode, ohne vorher zu entscheiden, wie viel vom Ergebnis du brauchst.
Generator-Ausdrücke
Wenn du eine List Comprehension geschrieben hast, kennst du die Syntax schon — tausch die eckigen gegen runde Klammern:
squares_gen berechnet noch nichts. Es ist nur ein Rezept. Iteration führt das Rezept Schritt für Schritt aus.
Generator-Ausdrücke sind ideal als Argumente für Funktionen, die eine Iterable konsumieren:
Keine Zwischenliste. sum, max und any lesen Werte einzeln, genau das, was sie wollen.
Eine große Datei Zeile für Zeile lesen
Das ist der kanonische Praxisfall für Generatoren — eine Datei verarbeiten, die zu groß für den Speicher ist:
def parse_log_lines(path):
with open(path) as f:
for line in f:
if line.startswith("ERROR"):
yield line.rstrip()
for error in parse_log_lines("app.log"):
print(error)
Die Datei wird lazy gelesen. Jeder Aufruf des Generators holt eine Zeile von der Platte, filtert sie und gibt sie aus. Der Speicherverbrauch bleibt flach, unabhängig von der Dateigröße.
Einmal und fertig
Ein Generator hat einen einzigen Durchgang. Nach der Iteration bis zum Ende ist er erschöpft:
Die zweite Schleife gibt nichts aus. Der Generator hat nichts mehr.
Musst du mehrfach iterieren, ruf die Generator-Funktion erneut auf oder materialisier die Sequenz mit list(...) und iterier die Liste. Entscheide nach Kosten: neu bauen ist okay, wenn die Arbeit günstig ist; eine Liste ist okay, wenn die Sequenz klein ist.
next() und manuelle Iteration
Du musst keine for-Schleife nutzen. next() zieht einen Wert nach dem anderen:
StopIteration ist, wie ein Generator „ich bin fertig“ signalisiert. for-Schleifen fangen es still ab. Im manuellen Code kannst du next(gen, default) übergeben, um die Ausnahme zu vermeiden.
Unendliche Generatoren
Weil Werte auf Abruf erzeugt werden, kann ein Generator eine Sequenz ohne Ende darstellen — solange der Konsument aufhört zu fragen:
while True mit einem yield drin hängt das Programm nicht auf — es heißt nur „wenn jemand weiter fragt, liefere weiter“. Der Konsument entscheidet, wann Schluss ist.
Das Muster taucht bei Streaming-Daten, Event-Loops und überall da auf, wo du Werte aus einer Quelle ohne definierte Länge ziehst.
yield from: an eine andere Iterable delegieren
Will dein Generator jeden Wert aus einer anderen Iterable liefern, macht yield from das in einer Zeile:
Ohne yield from würdest du eine verschachtelte for-Schleife mit yield x schreiben. Es reicht auch send()- und throw()-Aufrufe korrekt weiter, falls du die je nutzt — aber für alltäglichen Code: denk dran als „liefere jeden Wert aus diesem Ding“.
Wann du zu einem Generator greifst
Drei Signale, dass ein Generator das richtige Werkzeug ist:
- Die Sequenz ist groß, möglicherweise unendlich oder teuer in voller Form zu erzeugen.
- Die Konsumentin könnte vor dem Ende stoppen (ein
breakbeim ersten Treffer etwa). - Du willst Transformationen verketten — filtern, mappen, abschneiden —, ohne Zwischenlisten zu bauen.
Und wann nicht:
- Du brauchst Zufallszugriff (
seq[42]). Generatoren gehen nur vorwärts. - Du musst dieselbe Sequenz mehrfach iterieren. Nimm eine Liste.
- Die Sequenz ist klein und du hast sie schon. Eine List Comprehension ist einfacher.
Generatoren, List Comprehensions und schlichte Listen sind für verschiedene Jobs jeweils die richtige Antwort. Die Fertigkeit ist, ohne viel Nachdenken zu wählen — und der schnellste Weg, diesen Instinkt zu entwickeln, ist: bei jeder Iteration, die du schreibst, bemerken, ob „erst alles produzieren“ oder „einzeln produzieren“ besser passt.
Als Nächstes: Context Manager im Detail
Du hast jetzt die meisten Idiome gesehen, die Python für Iteration nutzt. Context Manager — die with-Anweisung — kommen als Nächstes und passen gut zu Generatoren, um Daten aus Dateien und Netzwerkverbindungen zu streamen.
Häufig gestellte Fragen
Was ist ein Generator in Python?
Ein Generator ist eine Funktion, die Werte einzeln produziert und dazwischen pausiert. Du schreibst ihn mit def wie eine normale Funktion, nutzt aber yield statt return. Der Aufruf liefert ein Generator-Objekt; jede Iteration eines for oder jeder next()-Aufruf läuft die Funktion bis zum nächsten yield.
Was ist der Unterschied zwischen einer Liste und einem Generator?
Eine Liste hält jedes Element gleichzeitig im Speicher. Ein Generator berechnet Elemente auf Abruf und vergisst sie nach dem Konsum. Für große oder unendliche Sequenzen nutzt ein Generator einen winzigen festen Speicherbedarf; für kleine Ergebnisse, die du mehrfach brauchst, ist eine Liste besser.
Kann ich einen Generator zweimal iterieren?
Nein. Ein Generator ist nach dem ersten Durchgang erschöpft — eine zweite for-Schleife darüber liefert nichts. Brauchst du mehr als einmal, ruf die Generator-Funktion erneut auf, um einen frischen Generator zu bekommen, oder materialisier die Ergebnisse in einer Liste.