0%

Abschlussprojekt: Eine gemeinschaftliche Leihbibliothek · Übung

Überfällige Ausleihen mit yield liefern

Eine Operation bleibt, und die Zusage beschreibt sie anders als die beiden übrigen:

overdue_loans(on_day) ist ein Iterable oder Generator.

available_items() und active_loans() geben beide Listen zurück. Diese Operation darf darauf verzichten. Es lohnt sich, den Unterschied zu verstehen, statt nur die Vorgabe zu erfüllen.

Was ein Generator ändert

Kapitel 11 entwickelte diese Idee. Eine Generatorfunktion läuft nicht bei ihrem Aufruf. Sie gibt etwas zurück, das Stück für Stück läuft, immer wenn du den nächsten Wert anforderst.

    def overdue_loans(self, on_day):
        for loan in self._loans.values():
            if loan.is_overdue(on_day):
                yield loan

Ein einziges yield macht die gesamte Methode zu einer Generatorfunktion. Nirgendwo gibt es eine Liste, und im Moment der Rückgabe wurde noch nichts untersucht.

Für einen Bericht über überfällige Ausleihen ist das anders wichtig als für die beiden übrigen Operationen. Meistens ist die Antwort leer, und ein Aufrufer möchte oft nur die ersten wenigen:

    for loan in library.overdue_loans(today):
        print(f"{loan.item_id} was due on day {loan.due_day}")
        break

Mit einer Liste wird jede Ausleihe untersucht, bevor die erste Zeile ausgegeben wird. Mit einem Generator werden Ausleihen untersucht, bis die erste überfällige gefunden ist. Ist keine überfällig, prüft er trotzdem jede aktive Ausleihe, erstellt aber nie eine Ergebnisliste.

Wo die Regel liegt

Beachte die Bedingung:

            if loan.is_overdue(on_day):

Nicht if on_day > loan.due_day. Lektion 4 platzierte diesen Vergleich auf Loan. Hier zahlt sich das aus: Die Entscheidung zwischen > und >= existiert genau einmal. Würde sie hier ausgeschrieben, gäbe es zwei Stellen für Fehler. Irgendwann würden sie auf unterschiedliche Weise falsch sein.

Der tatsächliche Preis

Ein Generator über self._loans liest den aktuellen Zustand. Er liest das Dictionary während des Durchlaufens. Gibt ein Aufrufer während der Iteration einen Gegenstand zurück, erhält er einen RuntimeError, weil sich die Größe des Dictionarys ändert.

Das ist ein echter Unterschied zu active_loans(), das kopiert. Es ist kein Grund, Generatoren zu meiden, aber ein Grund, zu wissen, was du herausgibst: list(library.overdue_loans(today)) erzeugt eine Momentaufnahme, wenn es vor Änderungen an der Bibliothek vollständig ausgewertet wird. Diese Wahl steht dem Aufrufer zu, statt dass du sie ihm aufzwingst.

Warum lässt sich active_loans() durchlaufen, während Gegenstände zurückgegeben werden, overdue_loans() dagegen nicht?

Vier Dateien und warum genau diese vier

Nach dieser Lektion ist das Projekt fertig. Deshalb lohnt sich ein Blick auf die entstandene Form.

models.py enthält die Begriffe: das, worum es im Programm geht. Es importiert nichts aus dem restlichen Projekt.

errors.py enthält die Ablehnungen der Bibliothek. Es importiert überhaupt nichts.

library.py enthält das Verhalten und importiert beide.

main.py ist das Programm und importiert alles, was es zur Ausführung braucht.

Alle Pfeile zeigen in dieselbe Richtung, vom Programm zu den Begriffen, keiner zurück. Für diese Form plädierte Kapitel 10. Ihre Prüfung ist einfach: models.py lässt sich ohne alle anderen Dateien verstehen, testen und wiederverwenden. Das wäre bei einer Anordnung, in der die Domäne die sie verwaltende Bibliothek kennt, nicht der Fall.

Aufgabe

Implementiere overdue_loans in library.py, die letzte Operation der Schnittstellenzusage.

overdue_loans(on_day) liefert jede aktive Ausleihe, die an diesem Tag überfällig ist, einzeln. Verwende yield, statt eine Liste aufzubauen: Ein Bericht über überfällige Ausleihen ist meistens leer, und ein Aufrufer möchte oft nur die ersten wenigen.

Frage jede Ausleihe, ob sie überfällig ist, statt hier Tage zu vergleichen. Loan.is_overdue ist bereits für diese Entscheidung zuständig. Sie soll an genau dieser einen Stelle bleiben.

Sonst muss in library.py nichts geändert werden. Das ist die letzte Änderung an dieser Datei.