0%

Iteration als Schnittstelle · Übung

Ein Domänenobjekt iterierbar machen

Kapitel 6 gab Quiz eine Methode, um seine Fragen herauszugeben:

for question in quiz.questions():
    print(question.prompt)

Das bleibt nützlich, wenn aufrufender Code eine Momentaufnahme als Liste braucht. Soll er nur durchlaufen, kann das Objekt Iteration direkt anbieten:

for question in quiz:
    print(question.prompt)

An etwas bereits Iterierbares delegieren

Quiz hält eine Liste, und eine Liste weiß bereits, wie sie durchlaufen wird. __iter__ muss deshalb nichts erfinden:

Try it

Eine Zeile und nirgends __next__. iter(self._questions) fordert einen neuen Iterator von der Liste an und gibt ihn zurück. Damit wird das Quiz ohne weitere Arbeit wiederholt durchlaufbar. Jeder Durchlauf erhält seinen eigenen Durchläufer, aus den in Lektion 3 erklärten Gründen.

Das schreibst du in der Praxis. Die Version mit zwei Klassen aus Lektion 3 zeigt dir, was iter() auf dieser Liste tut. Fast jedes echte __iter__ besteht aus dieser einen Zeile.

Was das bringt

Der Gewinn liegt nicht darin, dass for question in quiz drei Zeichen kürzer ist. Quiz funktioniert nun mit allem, was das Iterationsprotokoll spricht, ohne dass eines dieser Werkzeuge ein Quiz kennen muss:

Try it

sorted, in, enumerate, zip, min, max, sum, any, all und das Entpacken von Tupeln können alle ein Iterable verarbeiten. Keines davon wurde speziell für Quiz geschrieben. Sie fordern einen Iterator an, und Quiz antwortet. Die Elemente müssen weiterhin zum Vorgang passen: Dieses Quiz enthält beispielsweise Strings. Du kannst seine Fragen sortieren, aber nicht mit sum addieren.

Das ist der Kapiteltitel: Iteration ist eine Schnittstelle. Implementiere eine Methode, und ein großer Teil der Sprache akzeptiert dein Objekt.

Warum lässt sich ein Quiz, dessen __iter__ den Wert iter(self._questions) zurückgibt, zweimal durchlaufen, der Countdown aus der vorherigen Lektion aber nicht?

Noch einmal: eine Kopie herausgeben

Kapitel 6 gab bewusst list(self._questions) zurück, damit aufrufender Code das Quiz nicht über die erhaltene Liste leeren konnte. Öffnet __iter__ diese Lücke wieder?

Nicht über den Iterator selbst. Ein Iterator liefert Elemente einzeln. Das Protokoll bietet keinen Vorgang zum Hinzufügen oder Entfernen von Einträgen aus dem Bestand. Die gelieferten Objekte können weiterhin veränderbar sein. for question in quiz schützt daher die Struktur der Sammlung, verspricht aber keinen tiefen Schreibschutz.

Damit ist __iter__ hier besser geeignet als questions(): Es bietet einen natürlichen Strom von Fragen, ohne eine Liste zum Messen, Indizieren oder Ändern herauszugeben. Behalte eine Methode questions() nur, wenn aufrufender Code diese folgenartige Schnittstelle tatsächlich benötigt.

Entscheiden, was durchlaufen wird

Ein Objekt mit mehreren Sammlungen muss entscheiden, was for x in obj bedeutet. Die Antwort sollte naheliegen. Ein Quiz durchläuft seine Fragen, ein Gradebook seine Datensätze. Gibt es keine offensichtliche Wahl, definiere kein __iter__: Gib den Sammlungen Namen und lass aufrufenden Code for result in book.records() schreiben.

__iter__ verspricht das Durchlaufen einer bestimmten Elementart. Es verspricht weder Indexzugriff noch eine Länge. Das sind eigene Folgenoperationen.

Aufgabe

Mache beide Klassen iterierbar, indem sie an die bereits enthaltene Sammlung delegieren.

Quiz.__iter__ durchläuft seine Fragen, Gradebook.__iter__ seine Datensätze. Jede Methode besteht aus einer Zeile, und keine braucht ein __next__.

Beide müssen wiederholt durchlaufbar bleiben: Zweimal durchlaufen zeigt zweimal alles.

Schreibe dann zwei Funktionen, die das Protokoll statt eines Methodennamens verwenden:

  • total_points(quiz) summiert die Punkte jeder Frage und verwendet dabei das Quiz direkt.

  • top_record(book) gibt den Datensatz mit dem höchsten Anteil zurück oder None, wenn das Notenbuch leer ist.

Keine Funktion darf .questions() oder .records() aufrufen. Die Bewertung übergibt neben einem Quiz und einem Gradebook auch eine gewöhnliche Liste. Das funktioniert nur, wenn die Funktionen ausschließlich Iteration verlangen.