Aufwärmen · Aufgabe 1 von 7
// 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.
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
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))Ü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 += 2nÜ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()))Ü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()))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")))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.pyAusgabe
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
Hinweis 1
Ersetzen Sie totals.append(total) durch yield total und löschen Sie die Liste.
Hinweis 2
Ein Generator gibt keine Liste zurück: Sein Rückgabetyp ist Iterator[int], importiert aus collections.abc.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.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
Hinweis 1
isinstance(item, list) sagt Ihnen, ob ein Element selbst eine Liste ist.
Hinweis 2
Für eine Liste ist flatten(item) der Generator dieser inneren Liste. Reichen Sie all seine Werte mit yield from weiter.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.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
Hinweis 1
Der Starter liefert einmal und endet dann. Setzen Sie das yield in eine Schleife, die nie aufhört: while True:.
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.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHä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 subscriptableWarum, 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 functionWarum, 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.