Zum Inhalt springen
aviral gupta

// I3.2 · ca. 32 Min. · Aufbau

Generatoren

Nach dieser Lektion schreiben Sie Generatorfunktionen mit yield, sagen vorher, wann ihr Code läuft und was sie liefern, und reichen Arbeit mit yield from an ein anderes Iterable weiter.

Lektion 2 von 6 in I3 Iteration und funktionale Werkzeuge

Danach können Sie

  • Eine Generatorfunktion mit yield schreiben und die gelieferten Werte vorhersagen
  • Erklären, wie ein Generator bei yield pausiert und seinen lokalen Zustand zwischen next()-Aufrufen behält
  • Mit yield from an ein anderes Iterable oder einen Generator weiterreichen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Lektion I3.1: Welche zwei Methoden braucht ein Iterator? Wählen Sie beide.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: In welcher Reihenfolge erscheinen die Wörter?

    def gen():
        print("start", end=" ")
        yield 1
        print("middle", end=" ")
        yield 2
    
    g = gen()
    print("created", end=" ")
    print(next(g))
  3. Üben · Aufgabe 3 von 7

    Setzen Sie das Schlüsselwort ein, das jede gerade Zahl herausgibt und die Schleife weiterlaufen lässt.

    def evens(limit):
        n = 0
        while n < limit:
            ____ n
            n += 2
    n
  4. Üben · Aufgabe 4 von 7

    Jeder Aufruf von counter() erzeugt einen neuen Generator. Was gibt dieser Code aus?

    def counter():
        count = 0
        while True:
            count += 1
            yield count
    
    c = counter()
    next(c)
    next(c)
    print(next(c), next(counter()))
  5. Üben · Aufgabe 5 von 7

    Was gibt dieser Code aus?

    def inner():
        yield 1
        yield 2
    
    def outer():
        yield 0
        yield from inner()
        yield 3
    
    print(list(outer()))
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Ein Generator mit einem return darin. Was gibt dieser Code aus?

    def first_two(items):
        for item in items:
            yield item
            if item == "b":
                return "done"
    
    print(list(first_two("abcd")))
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie einen Generator fib(limit), der die Fibonacci-Zahlen unter limit liefert: 0, 1, 1, 2, 3, 5, …, wobei jede Zahl die Summe der beiden vorigen ist. Halten Sie die letzten zwei Zahlen in lokalen Variablen. Geben Sie list(fib(50)) und list(fib(1)) aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine verschachtelte To-do-Liste nummerieren

Zwei kleine Generatoren arbeiten zusammen. flatten durchläuft eine verschachtelte Liste und wendet für jede innere Liste yield from auf sich selbst an. numbered nimmt ein beliebiges Iterable und liefert Zeilen „n. item“, wobei sein Zähler n zwischen den Aufrufen erhalten bleibt. Keiner der beiden baut eine Liste: Jede Zeile entsteht, wenn die Schleife danach fragt. Ein Generator ist ein Iterator und wird als Iterator[...] aus collections.abc annotiert.

main.py

from collections.abc import Iterable, Iterator


def flatten(items: list[object]) -> Iterator[object]:
    """Yield every item of a nested list, depth first."""
    for item in items:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item


def numbered(lines: Iterable[object], start: int = 1) -> Iterator[str]:
    """Yield "n. line" for each line, counting from start."""
    n = start
    for line in lines:
        yield f"{n}. {line}"
        n += 1


todo: list[object] = ["shop", ["milk", "eggs", ["free-range"]], "call mum"]
steps = numbered(flatten(todo))
print(type(steps).__name__)
print(next(steps))
for line in steps:
    print(line)

Ausführen mit

python main.py

Ausgabe

generator
1. shop
2. milk
3. eggs
4. free-range
5. call mum
  • numbered(flatten(todo)) führte noch keinen Code aus, sondern erzeugte nur einen Generator, wie die erste Zeile zeigt.
  • next(steps) nahm eine Zeile; die for-Schleife machte mit demselben Generator bei 2 weiter.
  • free-range steckt zwei Listen tief, und yield from reichte es durch beide Ebenen nach oben.
  • n zählte zwischen den Zeilen weiter, weil der Generator seine lokalen Variablen behält, während er pausiert.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 3

Laufende Summen, eine nach der anderen

running_total(numbers) im Starter baut eine ganze Liste laufender Summen: Für [1, 2, 3, 4] liefert es 1, 3, 6 und 10. Machen Sie daraus einen Generator, der jede Summe liefert, sobald sie feststeht, und keine Liste baut. Die Tests prüfen die Werte und dass running_total einen Generator zurückgibt.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Ersetzen Sie totals.append(total) durch yield total und löschen Sie die Liste.

  2. Hinweis 2

    Ein Generator gibt keine Liste zurück: Sein Rückgabetyp ist Iterator[int], importiert aus collections.abc.

  3. Hinweis 3

    total = 0; for n in numbers: total += n; yield total.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from collections.abc import Iterable, Iterator


def running_total(numbers: Iterable[int]) -> Iterator[int]:
    """Yield the running totals of numbers, one at a time."""
    total = 0
    for n in numbers:
        total += n
        yield total


if __name__ == "__main__":
    print(list(running_total([1, 2, 3, 4])))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from collections.abc import Iterable


def running_total(numbers: Iterable[int]) -> list[int]:
    """Return the running totals of numbers."""
    totals = []
    total = 0
    for n in numbers:
        total += n
        totals.append(total)
    return totals


if __name__ == "__main__":
    print(list(running_total([1, 2, 3, 4])))

test_main.py

from types import GeneratorType

from main import running_total


def test_totals():
    """[1, 2, 3, 4] ergibt 1, 3, 6, 10"""
    got = list(running_total([1, 2, 3, 4]))
    assert got == [1, 3, 6, 10], f"list(running_total([1, 2, 3, 4])) ergab {got!r}, erwartet: [1, 3, 6, 10]"


def test_empty():
    """Keine Zahlen ergeben keine Summen"""
    got = list(running_total([]))
    assert got == [], f"list(running_total([])) ergab {got!r}, erwartet: []"


def test_is_generator():
    """running_total gibt einen Generator zurück"""
    got = running_total([1, 2])
    assert isinstance(got, GeneratorType), f"running_total gab ein {type(got).__name__} zurück, keinen Generator: Verwenden Sie yield"


def test_one_at_a_time():
    """next() liefert die Summen einzeln"""
    g = running_total(iter([5, 5, 5]))
    got = next(g), next(g)
    assert got == (5, 10), f"zwei next()-Aufrufe ergaben {got!r}, erwartet: (5, 10)"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 3

Flach machen mit yield from

Schreiben Sie einen Generator flatten(items), der jedes Element einer verschachtelten Liste der Reihe nach liefert, egal wie tief: flatten([1, [2, [3, 4]], 5]) ergibt 1, 2, 3, 4, 5. Für ein Element, das eine Liste ist, wenden Sie yield from auf flatten selbst an; jedes andere Element liefern Sie unverändert. Ein String zählt als ein Element.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    isinstance(item, list) sagt Ihnen, ob ein Element selbst eine Liste ist.

  2. Hinweis 2

    Für eine Liste ist flatten(item) der Generator dieser inneren Liste. Reichen Sie all seine Werte mit yield from weiter.

  3. Hinweis 3

    if isinstance(item, list): yield from flatten(item) else: yield item.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from collections.abc import Iterator


def flatten(items: list[object]) -> Iterator[object]:
    """Yield every item of a nested list, depth first."""
    for item in items:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item


if __name__ == "__main__":
    print(list(flatten([1, [2, [3, 4]], 5])))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from collections.abc import Iterator


def flatten(items: list[object]) -> Iterator[object]:
    """Yield every item of a nested list, depth first."""
    for item in items:
        yield item


if __name__ == "__main__":
    print(list(flatten([1, [2, [3, 4]], 5])))

test_main.py

from main import flatten


def test_flat_list():
    """Eine flache Liste bleibt unverändert"""
    got = list(flatten([1, 2, 3]))
    assert got == [1, 2, 3], f"list(flatten([1, 2, 3])) ergab {got!r}"


def test_nested():
    """[1, [2, [3, 4]], 5] ergibt 1, 2, 3, 4, 5"""
    got = list(flatten([1, [2, [3, 4]], 5]))
    assert got == [1, 2, 3, 4, 5], f"list(flatten([1, [2, [3, 4]], 5])) ergab {got!r}, erwartet: [1, 2, 3, 4, 5]"


def test_empty_lists():
    """Leere innere Listen fügen nichts hinzu"""
    got = list(flatten([[], [1, []], []]))
    assert got == [1], f"list(flatten([[], [1, []], []])) ergab {got!r}, erwartet: [1]"


def test_strings_stay_whole():
    """Ein String ist ein Element und wird nicht in Buchstaben zerlegt"""
    got = list(flatten(["ab", ["cd"]]))
    assert got == ["ab", "cd"], f"list(flatten(['ab', ['cd']])) ergab {got!r}, erwartet: ['ab', 'cd']"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 3 von 3

Ein endloser Id-Generator

Schreiben Sie einen Generator ids(prefix), der ohne Ende prefix1, prefix2, prefix3 und so weiter liefert: ids("u") ergibt "u1", "u2", "u3", …. Halten Sie den Zähler in einer lokalen Variablen innerhalb einer Schleife while True. Zwei Generatoren müssen unabhängig voneinander zählen.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Der Starter liefert einmal und endet dann. Setzen Sie das yield in eine Schleife, die nie aufhört: while True:.

  2. Hinweis 2

    Rücken Sie den Zähler nach dem yield mit n += 1 weiter; der Generator pausiert bei yield, also läuft das erst, wenn die nächste Id verlangt wird.

  3. Hinweis 3

    Jeder Aufruf von ids() hat sein eigenes n, zwei Generatoren bringen ihre Zählung also nie durcheinander.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from collections.abc import Iterator


def ids(prefix: str) -> Iterator[str]:
    """Yield prefix1, prefix2, prefix3, ... without end."""
    n = 1
    while True:
        yield f"{prefix}{n}"
        n += 1


if __name__ == "__main__":
    new_id = ids("u")
    print(next(new_id), next(new_id))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from collections.abc import Iterator


def ids(prefix: str) -> Iterator[str]:
    """Yield prefix1, prefix2, prefix3, ... without end."""
    n = 1
    yield f"{prefix}{n}"


if __name__ == "__main__":
    new_id = ids("u")
    print(next(new_id), next(new_id))

test_main.py

from main import ids


def test_first_three():
    """ids('u') ergibt u1, u2, u3"""
    g = ids("u")
    got = [next(g), next(g), next(g)]
    assert got == ["u1", "u2", "u3"], f"drei next()-Aufrufe ergaben {got!r}, erwartet: ['u1', 'u2', 'u3']"


def test_keeps_going():
    """Auch die 100. Id kommt noch"""
    g = ids("x")
    for _ in range(99):
        next(g)
    got = next(g, None)
    assert got == "x100", f"die 100. Id war {got!r}, erwartet: 'x100'"


def test_independent():
    """Zwei Generatoren zählen jeder für sich"""
    a, b = ids("a"), ids("b")
    next(a)
    next(a)
    got = next(b), next(a)
    assert got == ("b1", "a3"), f"Ergebnis {got!r}, erwartet: ('b1', 'a3')"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Einen Generator nach seiner Länge fragen

def squares(n):
    for i in range(n):
        yield i * i


print(len(squares(5)))

Was Python ausgibt

TypeError: object of type 'generator' has no len()

Warum, und die Lösung

Ein Generator erzeugt seine Werte auf Anfrage und weiß darum nicht, wie viele es werden. Brauchen Sie die Anzahl oder die Werte zweimal, machen Sie zuerst eine Liste daraus: values = list(squares(5)), dann len(values). Brauchen Sie nur die Anzahl, zählt sum(1 for _ in gen), ohne die Werte zu behalten.

Einen Generator indizieren

def squares(n):
    for i in range(n):
        yield i * i


print(squares(5)[0])

Was Python ausgibt

TypeError: 'generator' object is not subscriptable

Warum, und die Lösung

Ein Generator hat keine Positionen zum Nachschlagen, nur einen nächsten Wert. Nehmen Sie next(squares(5)) für den ersten Wert, oder list(squares(5))[0], wenn Sie wirklich wahlfreien Zugriff brauchen.

yield außerhalb einer Funktion

numbers = [1, 2, 3]
for n in numbers:
    yield n * 2

Was Python ausgibt

SyntaxError: 'yield' outside function

Warum, und die Lösung

yield ergibt nur in einer Funktion Sinn, die es zur Generatorfunktion macht. Legen Sie die Schleife in def doubled(numbers): und rufen Sie sie auf, oder schreiben Sie für ein schnelles Ergebnis eine List Comprehension: [n * 2 for n in numbers].

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

yield macht eine Generatorfunktion

Eine Funktion, die yield enthält, ist eine Generatorfunktion. Ihr Aufruf führt den Rumpf nicht aus, sondern liefert ein Generator-Objekt, und das ist ein Iterator. Jedes next() führt den Rumpf bis zum nächsten yield aus, gibt diesen Wert heraus und pausiert dort. Endet der Rumpf, weil er am Ende ankommt oder auf ein nacktes return trifft, löst der Generator StopIteration aus. Eine for-Schleife, list() oder sum() verbrauchen ihn also wie jeden Iterator, und Sie schreiben nie __iter__ oder __next__: Der Generator hat sie schon.

Pausiert, nicht beendet

Zwischen zwei next()-Aufrufen behält ein Generator alles: seine lokalen Variablen und die Stelle in einer Schleife. Darum ist er viel kürzer als eine Iterator-Klasse, in der Sie die Position selbst in self.index speichern. Außerdem wird erst gearbeitet, wenn ein Wert verlangt wird: Code vor dem ersten yield läuft beim ersten next(), nicht beim Aufruf. Eine Schleife while True ist in Ordnung, denn es entstehen nur die Werte, nach denen jemand fragt. Wie jeder Iterator ist ein Generator nach einem Durchlauf verbraucht; rufen Sie die Funktion für einen neuen erneut auf.

yield from

yield from iterable gibt jeden Wert eines anderen Iterables einzeln heraus, als hätten Sie for x in iterable: yield x geschrieben. Das Iterable kann eine Liste, ein String oder ein anderer Generator sein. So lässt sich ein Generator in Teile zerlegen, und verschachtelte Daten werden einfach: Ein Generator, der eine verschachtelte Liste durchläuft, kann für jede innere Liste yield from auf sich selbst anwenden. Verwechseln Sie es nicht mit yield: yield [1, 2] gibt die Liste als einen Wert heraus, yield from [1, 2] erst 1 und dann 2.

Quellen

Zuletzt geprüft am 29. September 2026