Generatoren und yield in Python

Generatoren erzeugen Werte Stück für Stück und sparen dabei Speicher. In diesem Beitrag lernst du, wie yield funktioniert und wann sich Generatoren lohnen.

Teilen
Generatoren und yield in Python

Stell dir vor, du möchtest über Millionen von Zahlen iterieren oder eine riesige Datei zeilenweise verarbeiten. Würdest du dafür eine komplette Liste im Arbeitsspeicher aufbauen, könnte dir schnell der Speicher ausgehen. Genau hier glänzen Generatoren. Sie erzeugen Werte erst dann, wenn sie gebraucht werden, und das mit dem Schlüsselwort yield. In diesem Beitrag schauen wir uns an, wie das funktioniert und warum Generatoren so elegant sind.

Der Unterschied zwischen return und yield

Eine normale Funktion gibt mit return einen einzigen Wert zurück und ist danach beendet. Eine Generator-Funktion verwendet stattdessen yield. Dabei wird der Zustand der Funktion gemerkt, und beim nächsten Aufruf läuft sie genau dort weiter.

def zaehle_bis(n):
    zahl = 1
    while zahl <= n:
        yield zahl
        zahl += 1

for wert in zaehle_bis(3):
    print(wert)
# 1
# 2
# 3

Die Funktion zaehle_bis liefert ihre Werte nacheinander. Nach jedem yield pausiert sie, bis der nächste Wert angefordert wird.

Lazy Evaluation: Werte erst bei Bedarf

Generatoren arbeiten nach dem Prinzip der verzögerten Auswertung (englisch lazy evaluation). Sie berechnen nicht alles im Voraus, sondern immer nur den nächsten Wert. Das spart Speicher, besonders bei großen Datenmengen.

def quadratzahlen():
    zahl = 1
    while True:
        yield zahl * zahl
        zahl += 1

gen = quadratzahlen()
print(next(gen))  # 1
print(next(gen))  # 4
print(next(gen))  # 9

Dieser Generator ist theoretisch unendlich. Trotzdem belegt er kaum Speicher, weil immer nur ein Wert gleichzeitig existiert. Mit next() holst du den jeweils nächsten Wert ab.

Generator-Ausdrücke

Ähnlich wie List Comprehensions gibt es auch Generator-Ausdrücke. Sie sehen fast identisch aus, verwenden aber runde statt eckige Klammern. Der Vorteil: Es wird keine vollständige Liste im Speicher erzeugt.

# List Comprehension: erzeugt sofort die ganze Liste
quadrate_liste = [x * x for x in range(1000000)]

# Generator-Ausdruck: erzeugt Werte erst bei Bedarf
quadrate_gen = (x * x for x in range(1000000))

print(sum(quadrate_gen))  # spart Speicher

Wenn du das Ergebnis nur einmal durchlaufen willst, ist der Generator-Ausdruck oft die bessere Wahl.

Eine große Datei verarbeiten

Ein praktisches Einsatzgebiet ist das Lesen großer Dateien. Statt die ganze Datei einzulesen, verarbeitest du sie Zeile für Zeile. So bleibt der Speicherbedarf konstant, egal wie groß die Datei ist.

def lies_zeilen(dateiname):
    with open(dateiname, encoding="utf-8") as datei:
        for zeile in datei:
            yield zeile.strip()

for zeile in lies_zeilen("logdatei.txt"):
    if "FEHLER" in zeile:
        print(zeile)

Interessant: Auch das Datei-Objekt selbst ist bereits ein Iterator, der zeilenweise liefert. Generatoren bauen genau auf diesem Prinzip auf.

Werte aus anderen Generatoren übernehmen

Mit yield from kannst du die Werte eines anderen Iterables direkt durchreichen. Das macht den Code kürzer und lesbarer, wenn du Generatoren verschachteln willst.

def buchstaben():
    yield from "abc"

def zahlen():
    yield from range(1, 4)

def kombiniert():
    yield from buchstaben()
    yield from zahlen()

print(list(kombiniert()))  # ['a', 'b', 'c', 1, 2, 3]

Mit list() kannst du einen Generator bei Bedarf in eine echte Liste umwandeln, wenn du alle Werte gleichzeitig brauchst.

Fazit

Generatoren sind ein eleganter Weg, um Werte Stück für Stück zu erzeugen, statt alles auf einmal im Speicher zu halten. Mit yield merkt sich die Funktion ihren Zustand und liefert beim nächsten Aufruf den nächsten Wert. Du hast gesehen, wie sich Generatoren von normalen Funktionen unterscheiden, wie Generator-Ausdrücke funktionieren und wie du große Dateien speicherschonend verarbeitest. Immer wenn du es mit großen oder potenziell unendlichen Datenmengen zu tun hast, sind Generatoren dein Freund. Probiere sie ruhig an deinen eigenen Datensätzen aus.