0%

Kapitel 11 · Übung

Iteration als Schnittstelle

Iterable und Iterator

Du hast Hunderte for-Schleifen geschrieben. Hier siehst du, was eine davon tatsächlich tut.

for question in questions:
    print(question.prompt)

Python durchläuft questions nicht anhand eines Index. Es bittet questions um einen Iterator und fragt diesen dann nach jeweils einem Element, bis er meldet, dass keines mehr übrig ist. Die Schleife ist ein Gespräch mit zwei Beteiligten. Wenn du sie unterscheiden kannst, verstehst du eine ganze Gruppe verwirrender Verhaltensweisen.

Zwei verschiedene Dinge

Ein Iterable, ein iterierbares Objekt, ist alles, was du in einer Schleife durchlaufen kannst: eine Liste, ein String, ein Dictionary, ein Set, eine Datei.

Ein Iterator übernimmt das Durchlaufen. Er hält die Position und weiß, was als Nächstes kommt.

Meist sind beide nicht dasselbe Objekt:

Try it

iter() bittet ein Iterable um einen Iterator. Eine Liste ist nicht ihr eigener Iterator. Sie gibt bei jeder Anfrage einen neuen heraus. Ein Iterator gibt dagegen sich selbst zurück und behält seine Position. Er ist deshalb ebenfalls iterierbar:

Try it

Das ist der Entwurf. Seine Folge siehst du sofort.

Eine Liste ist ein Iterable, das einen neuen Iterator erzeugt. Der Iterator bewegt sich jeweils ein Element weiter.

Ein Iterator wird verbraucht

Try it

Das erste list(walker) sammelte alle drei Elemente. Das zweite sammelte nichts. Der Iterator sprang nicht zurück, denn er hat keinen Rückweg: Er hält nur eine Position, und diese liegt jetzt hinter dem Ende.

Die Liste selbst ist unverändert:

Try it

Beide Aufrufe liefern drei Elemente, denn jeder list()-Aufruf hat questions um einen neuen Iterator ab dem Anfang gebeten.

Warum lässt sich dieselbe Liste zweimal durchlaufen, derselbe Iterator aber nicht?

Wo das Probleme macht

Das ist kein nebensächliches Detail. Es verursacht einen bestimmten Fehler, bei dem Daten scheinbar verschwinden:

Try it

Dieselben Zahlen, zwei verschiedene Antworten. Bei einer Liste erhält sum() einen Iterator und verbraucht ihn. Danach fordert list(scores) einen neuen von der Liste an: drei Elemente. Bei einem Iterator verbraucht sum() den einzigen vorhandenen, und list(scores) findet nichts mehr.

Die Funktion ist weniger falsch als unzureichend beschrieben: Sie durchläuft ihr Argument zweimal, ohne das zu sagen. Schreibst du eine Funktion, die ihre Eingabe mehrmals durchläuft, dokumentiere entweder die nötige wiederholt durchlaufbare Sammlung oder wandle die Eingabe zuerst in eine Liste um und benenne den Aufwand ausdrücklich.

Warum überhaupt diese Trennung

Es wäre einfacher, wenn eine Liste sich selbst durchliefe. Die Trennung bringt zwei Vorteile.

Mehrere Schleifen gleichzeitig. Zwei verschachtelte Schleifen über dieselbe Liste erhalten jeweils eine eigene Position. Das Ende der inneren beendet deshalb nicht die äußere.

Try it

Neun, nicht drei. Mit einer einzigen gemeinsamen Position könnte das nicht funktionieren.

Dinge, die zu groß zum Speichern sind. Ein Iterator muss nur wissen, wie er das nächste Element erzeugt. Nicht alle müssen gleichzeitig existieren. So kann ein Programm eine Datei, die größer als der Arbeitsspeicher ist, zeilenweise lesen. Lektion 6 greift das auf.

Die nächste Lektion führt das Gespräch von Hand. Damit wird das Protokoll von einer Beschreibung zu etwas, das du selbst verwendet hast.

Aufgabe

Schreibe drei kleine Funktionen, die ein Iterable und einen Iterator anhand ihres Verhaltens unterscheiden, nicht anhand ihres Typnamens.

  • walks_twice(items) gibt ein Tupel aus zwei Listen zurück: alles aus einem ersten und alles aus einem zweiten Durchlauf. Eine Liste ergibt zwei volle Listen, ein Iterator eine volle und eine leere.

  • is_own_iterator(items) meldet, ob die Anfrage nach einem Iterator das Objekt selbst zurückgibt. Bei einem Iterator ist das wahr, bei einer Liste falsch.

  • safe_count(items) gibt die Anzahl der Elemente zurück und muss für beide funktionieren. Eine Lösung, die ihre Eingabe zweimal durchläuft, kann das nicht leisten.

Führe das Programm aus, um jede Antwort für eine Liste und einen Iterator über dieselben drei Werte zu sehen.