Iteration als Schnittstelle · Abschlussprojekt
Abschlussprojekt: Projekt: Einen iterierbaren Fragenbestand bauen
Frühere Projekte gaben Momentaufnahmen als Listen über benannte Methoden heraus. Dieses Projekt kombiniert das Iterationsprotokoll der letzten Lektionen mit verzögertem Filtern und begrenztem Verbrauch.
Was du baust
Einen QuestionBank, der Fragen hält und drei Arten von Anfragen beantwortet:
for question in bank every graded question, in order
bank.by_topic("loops") a lazy view, filtered
bank.sample(3) at most three, from any source
Das erste ist __iter__. Das zweite und dritte sind Generatoren. Keines baut eine Liste auf, sofern der aufrufende Code keine Liste verlangt.
Die Entscheidungen
__iter__ überspringt unbewertete Fragen. Ein Bestand enthält auch Umfrageelemente. Ihn zu durchlaufen bedeutet, die zählenden Fragen zu durchlaufen. Das ist eine echte Entscheidung. Deshalb ist __iter__ hier ein Generator statt der einzeiligen Delegation aus Lektion 4.
by_topic gibt einen Generator zurück und sagt das. Aufrufender Code möchte vielleicht nur den ersten Treffer. Diese Form erlaubt es, dann aufzuhören.
sample hat eine exakte Grenze. Die Anfrage nach zwei Fragen liefert und verbraucht zwei, nicht drei. Bei null oder einer negativen Anzahl beginnt kein Durchlauf des Bestands.
summary gibt eine Liste zurück. Sie ist kurz, und aufrufender Code wird sie indizieren und messen. Ein Generator wäre ohne Nutzen umständlicher. Das ist die angewandte Regel aus Lektion 6.
Warum ist __iter__ hier als Generator statt mit return iter(self._questions) geschrieben?
Regeln, die die Bewertung prüft
Der Bestand ist wiederholt durchlaufbar: Zwei Durchläufe sehen zweimal alles.
by_topicundsamplesind Generatorfunktionen, keine Funktionen mit Listen als Rückgabewert.samplefunktioniert mit einer endlosen Quelle und muss deshalb selbst aufhören.sampleverbraucht keine Frage über die angeforderte Anzahl hinaus.first_unanswereduntersucht nicht mehr Fragen als nötig.points_neededgibt0zurück, wenn das Ziel schon zu Beginn erreicht ist.summarygibt eine Liste zurück.
Fertigstellen
Lies deinen QuestionBank und frage für jede Methode, was aufrufender Code mit ihrem Ergebnis tun kann. Eine liefert etwas Messbares und Indizierbares. Die anderen liefern etwas, das einmal durchlaufen und früh beendet werden kann. Beides sind richtige Antworten auf unterschiedliche Fragen.
Aufgabe
Baue QuestionBank und die beiden verwendenden Funktionen.
add(question) speichert eine Frage. __iter__ ist ein Generator, der jede Frage mit mehr als null Punkten in ihrer Reihenfolge liefert und unbewertete Fragen überspringt.
by_topic(topic) ist ein Generator für bewertete Fragen mit passendem Thema. sample(count) ist ein Generator für höchstens count bewertete Fragen. Er fordert vom Iterator des Bestands nicht mehr bewertete Fragen an, als er liefert. Beim Finden dürfen weiterhin gespeicherte unbewertete Fragen untersucht werden. Bei null oder einer negativen Anzahl wird nichts verbraucht.
total_points ist eine schreibgeschützte Property, die die bewerteten Fragen mit einem Generatorausdruck statt einer Liste summiert.
summary() gibt eine Liste von Strings der Form "<prompt> (<points>)" zurück, weil aufrufender Code ihre Länge abfragt und sie indiziert.
Dann folgen zwei Funktionen, die jedes Iterable von Fragen entgegennehmen, nicht nur einen Fragenbestand:
first_unanswered(questions, answered)gibt die erste Frage zurück, deren Fragetext nicht inansweredenthalten ist, oderNone. Es muss sofort beim ersten Treffer aufhören.points_needed(questions, target)gibt zurück, wie viele Fragen in ihrer Reihenfolge benötigt werden, bis ihre Punktetargeterreichen. Wird das Ziel nie erreicht, lautet das ErgebnisNone. Gib beitarget <= 0den Wert0zurück, denn dieses Ziel ist schon vor dem Verbrauch einer Frage erreicht.