Iteration als Schnittstelle · Übung
Arbeit mit yield pausieren
Lektion 3 brauchte zwanzig Zeilen und zwei Klassen, um eine Liste zu durchlaufen. Hier ist dasselbe:
yield statt return. Dieses eine Wort verwandelt eine gewöhnliche Funktion in etwas, das das gesamte Iteratorprotokoll für dich bereitstellt.
Was yield tut
Eine Funktion mit yield ist eine Generatorfunktion. Ihr Aufruf führt den Funktionskörper nicht aus:
Er gibt ein Generatorobjekt zurück. Dieses Objekt ist ein Iterator: Es hat __iter__ und __next__, kann an for, list und sum übergeben werden und ist nach einem Durchlauf verbraucht.

Der Funktionskörper läuft erst, wenn etwas ein Element anfordert. Dann läuft er bis zum nächsten yield und hält dort an:
Lies die Ausgabe genau. „starting“ erscheint erst beim ersten next(), „between“ beim zweiten. Die Funktion läuft nicht einfach durch und kehrt zurück. Sie pausiert, behält alles und setzt dort fort, wo sie aufgehört hat.
Wenn die Funktion endet, löst der Generator selbst StopIteration aus. Das schreibst du nie von Hand.
Was passiert beim Aufruf einer Generatorfunktion?
Lokalen Zustand ohne Zusatzarbeit behalten
Diese Pause erhält deine lokalen Variablen. Deshalb ersetzt sie die Mechanik aus Lektion 3. Die Position, die du in self._index speichern musstest, ist nun einfach eine Stelle im Code:
total bleibt über die Pausen hinweg erhalten, ohne ein Attribut zu sein. Vergleiche das mit einem RunningTotalIterator mit __init__, __iter__, __next__, gespeichertem Index und gespeicherter Summe. Ein Beispiel liefert schon das Argument für Generatoren.
Ohne zugrunde liegende Liste iterieren
Ein Generator braucht keine Sammlung zum Durchlaufen. Er kann Werte erzeugen, die nie alle gleichzeitig existieren:
Nirgends gibt es eine Liste von Bezeichnungen. Jede entsteht auf Anfrage und wird nach der Verwendung verworfen. Das ist die Idee „zu groß zum Speichern“ aus Lektion 1, jetzt selbst schreibbar.
Eine Klasse mit yield iterierbar machen
__iter__ darf eine Generatorfunktion sein. Das ist der übersichtlichste Weg, etwas zu durchlaufen, das nicht bloß eine gespeicherte Liste ist:
Weiterhin wiederholt durchlaufbar, denn jeder Aufruf von __iter__ startet einen neuen Generator mit eigener Position. Muss __iter__ filtern, umwandeln oder verschachtelte Sammlungen zusammenführen, schreibe es so. Reicht es eine gespeicherte Sammlung weiter, ist return iter(self._questions) aus Lektion 4 kürzer und sagt genau das.
Worauf du achten solltest
Ein Generator wird wie jeder Iterator verbraucht. Dieser Fehler passiert leicht:
Das zweite sum erhält nichts. Brauchst du die Werte zweimal, baue einmal eine Liste auf und verwende sie erneut. Lektion 6 behandelt die Wahl der Ergebnisform und das Anhalten einer verzögert ausgewerteten Quelle an einer exakten Grenze.
Aufgabe
Implementiere einen Generator: passing(records, mark) liefert in Eingabereihenfolge jeden Datensatz, dessen percentage mindestens mark beträgt.
Der Aufruf von passing darf noch keinen Datensatz untersuchen. Die Arbeit beginnt erst, wenn der aufrufende Code ein Ergebnis anfordert. Die Auswertung muss so weit verzögert bleiben, dass sich ein bestandener Datensatz aus einer endlosen Eingabe entnehmen lässt.
Die vorgegebenen Funktionen running_total und numbered zeigen angesammelten Zustand und Umwandlung. Das vorgegebene QuestionBank.__iter__ zeigt eine wiederholt durchlaufbare Generatormethode. Lies und führe sie aus, aber lass sie unverändert.