0%

Iteration als Schnittstelle · Übung

Das Iteratorprotokoll verstehen

iter() und next() bilden die Seite des aufrufenden Codes. Diese Lektion zeigt die andere Seite: Was muss ein Objekt anbieten, damit es durchlaufen werden kann?

Es sind zwei Methoden. Ihre Namenskonvention kennst du bereits.

Die beiden Methoden

__iter__ gibt einen Iterator zurück. Python ruft es auf, wenn etwas einen Iterator anfordert, wie iter() und jede for-Schleife.

__next__ gibt das nächste Element zurück oder löst StopIteration aus, wenn keines übrig ist. Python ruft es auf, wenn etwas ein Element anfordert, wie next().

Ein Iterable definiert __iter__. Ein Iterator definiert beide Methoden, und sein __iter__ gibt ihn selbst zurück.

Hier ist das kleinste vollständige Beispiel:

Try it

Nirgends eine Liste. Die Zahlen existieren erst, wenn __next__ sie erzeugt. Die for-Schleife weiß davon nichts und muss es auch nicht.

Langsam lesen

__iter__ gibt self zurück. Genau das macht einen Iterator aus: Er ist der Durchläufer, also liefert eine entsprechende Anfrage ihn selbst. Das ist dasselbe iter(x) is x, das du in Lektion 1 getestet hast.

__next__ erledigt die Arbeit. Es prüft, ob etwas übrig ist, löst andernfalls StopIteration aus, verschiebt die Position und gibt das Element zurück. Jeder Iterator folgt dieser Form.

Beachte, was beim zweiten Durchlauf passiert:

Try it

Verbraucht, genau wie der Listeniterator aus Lektion 1. Countdown hält seine Position in self.current, und nichts setzt sie zurück. Es ist ein Iterator und deshalb nur einmal durchlaufbar.

Was muss __next__ tun, wenn nichts mehr übrig ist?

Iterable und Iterator als getrennte Objekte

Countdown ist beides zugleich und deshalb verbrauchbar. Soll ein Objekt wiederholt durchlaufbar sein, trenne die Rollen: Das Iterable erzeugt jedes Mal einen neuen Iterator.

Try it

Beide Durchläufe sehen beide Elemente, weil jeder list()-Aufruf einen neuen BankWalker mit eigenem _index erhielt. Der Bestand hält die Daten, der Durchläufer die Position. Genau so ist eine Liste mit ihrem eigenen Iterator angeordnet. Jetzt verstehst du warum.

Schreibe das nicht so

Zwei Klassen und ungefähr zwanzig Zeilen, um eine bereits vorhandene Liste zu durchlaufen. In echtem Code fast nie nötig.

Diese Lektion macht das Protokoll greifbar. Die nächsten beiden zeigen, was du stattdessen tatsächlich schreibst: __iter__, das an etwas bereits Iterierbares delegiert, und Generatoren, die diese gesamte Form aus einer gewöhnlichen Funktion erzeugen.

Die Übung baut einmal einen vollständigen Iterator von Hand.

Aufgabe

Schreibe das Protokoll einmal von Hand, damit es nicht bloß eine Beschreibung bleibt.

Countdown(start) ist ein Iterator, der start, start - 1 und so weiter bis 1 liefert und dann stoppt. Er definiert beide Methoden: __iter__ gibt ihn selbst zurück, __next__ liefert die nächste Zahl oder löst StopIteration aus. Ein Countdown(0) liefert überhaupt nichts.

ScoreLog(scores) ist ein Iterable, kein Iterator. Sein __iter__ gibt bei jeder Anfrage einen neuen LogWalker zurück, sodass sich dasselbe Protokoll zweimal durchlaufen lässt.

LogWalker(scores) ist der Iterator: Er hält die Position, gibt sich aus __iter__ selbst zurück und löst nach dem Ende StopIteration aus.

Die Bewertung durchläuft ein ScoreLog zweimal und erwartet bei beiden Durchläufen alle Elemente. Sie durchläuft einen Countdown zweimal und erwartet beim zweiten nichts. Genau um diesen Unterschied geht es.