0%

Iteration als Schnittstelle · Übung

Arbeit mit yield pausieren

Lektion 3 brauchte zwanzig Zeilen und zwei Klassen, um eine Liste zu durchlaufen. Hier ist dasselbe:

Try it

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:

Try it

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.

Ein Generator liefert bei jedem next-Aufruf einen Wert und pausiert bei yield bis zum nächsten Aufruf.

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:

Try it

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:

Try it

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:

Try it

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:

Try it

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:

Try it

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.