15. Anwendungsübung – Version 4

Hier greifen wir die im Abschnitt |Version 3| beschriebene Übung wieder auf und implementieren sie nun unter Verwendung von Klassen und Schnittstellen. Wir werden zwei Anwendungen schreiben:
Anwendung 1 sieht wie folgt aus:

Ein Hauptskript [main] instanziiert eine [DAO]-Schicht und eine [Business]-Schicht:
- Die [DAO]-Schicht ist für die Verwaltung der in Textdateien und später in einer Datenbank gespeicherten Daten zuständig;
- die [Business]-Schicht ist für die Berechnung der Steuer zuständig;
In dieser Anwendung gibt es keine Benutzereingaben: Die Steuerzahlerdaten befinden sich in einer Textdatei, deren Name an das [main]-Modul übergeben wird.
In Anwendung 2 gibt der Benutzer die Steuerzahlerdaten über die Tastatur ein. Die Architektur entwickelt sich dann wie folgt:

- Die [DAO]-Schicht (Data Access Object) verwaltet den Zugriff auf externe Daten
- Die [Business]-Schicht übernimmt die Geschäftslogik, in diesem Fall die Steuerberechnung. Sie verarbeitet die Daten nicht. Diese Daten können aus zwei Quellen stammen:
- der [DAO]-Schicht für persistente Daten;
- die [UI]-Schicht für vom Benutzer bereitgestellte Daten.
- Die [UI]-Schicht (User Interface) verwaltet die Interaktionen mit dem Benutzer;
- [main] fungiert als Koordinator;
Im Folgenden werden die Schichten [dao], [business] und [ui] jeweils mithilfe einer Klasse implementiert. Die Schichten [business] und [dao] sind für beide Anwendungen identisch. Aus diesem Grund wurden sie in einer einzigen Version der Anwendungsübung zusammengefasst.
15.1. Version 4 – Anwendung 1
Version 4 berechnet die Steuer für eine Liste von Steuerzahlern, die in einer Textdatei gespeichert ist. Sie weist folgende Architektur auf:

15.1.1. Entitäten

Entitäten sind Datenklassen. Ihre Aufgabe ist es, Daten zu kapseln und Getter/Setter bereitzustellen, mit denen die Gültigkeit der Daten überprüft werden kann. Entitäten werden zwischen den Schichten ausgetauscht. Eine einzelne Entität kann von der [ui]-Schicht zur [dao]-Schicht und umgekehrt wandern.
15.1.1.1. Die Klasse [ImpôtsError]
Wir werden eine benutzerdefinierte Ausnahmeklasse verwenden:
Sobald in den Schichten [business] und [DAO] ein Problem auftritt, wird diese Ausnahme ausgelöst. Sie leitet sich von der Klasse [MyException] ab. Sie wird daher wie folgt verwendet: [raise ImpôtsError(error_code, error_message)].
15.1.1.2. Die Klasse [AdminData]
Die Klasse [AdminData] kapselt die bei Steuerberechnungen verwendeten Konstanten:
- Zeile 5: Die Klasse [AdminData] erweitert die im Abschnitt |BaseEntity| beschriebene Klasse [BaseEntity]. Beachten Sie, dass Klassen, die die Klasse [BaseEntity] erweitern, Folgendes definieren müssen:
- ein Klassenattribut [excluded_keys] (Zeile 7), das die Eigenschaften des Objekts auflistet, die bei der Konvertierung des Objekts in ein Wörterbuch ausgeschlossen werden;
- eine statische Methode [get_allowed_keys] (Zeilen 10–26), die die Liste der Eigenschaften zurückgibt, die akzeptiert werden, wenn das Objekt mit einem Wörterbuch initialisiert wird;
Wir haben keine Setter verwendet, um die Daten zu validieren, die zur Initialisierung eines [AdminData]-Objekts verwendet werden. Der Grund dafür ist, dass dieses Objekt eindeutig ist und durch die Konfiguration definiert wird und daher wahrscheinlich keine Fehler enthält.
15.1.1.3. Die Klasse [TaxPayer]
Die Klasse [TaxPayer] modelliert einen Steuerzahler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
Anmerkungen:
- Die Klasse [TaxPayer] kapselt einen Steuerzahler;
- Zeile 7: Die Klasse [TaxPayer] leitet sich von der Klasse [BaseEntity] ab. Sie verfügt daher über einen Bezeichner [id];
- Zeile 20: Es sind keine Eigenschaften vom Zustand eines [AdminData]-Objekts ausgeschlossen;
- Zeilen 22–25: die Klassen-Eigenschaften. Diese werden in den Zeilen 9–17 erläutert;
- Zeilen 27–58: Getter für die Klassenattribute;
- Zeilen 60–161: die Setter für die Attribute der Klasse. Erinnern Sie sich daran, dass der Vorteil einer Klasse, die Daten kapseln, gegenüber einem einfachen Wörterbuch darin besteht, dass die Klasse die Gültigkeit ihrer Eigenschaften mithilfe ihrer Setter überprüfen kann;
15.1.2. Die [dao]-Schicht

Wir werden die Implementierungen der Schichten in einem Ordner [services] zusammenfassen. Diese Klassen werden Schnittstellen implementieren, die im Ordner [interfaces] definiert sind.

15.1.2.1. Die Schnittstelle [InterfaceImpôtsDao]
Die [dao]-Schicht wird die folgende [InterfaceImpôtsDao]-Schnittstelle implementieren (Datei InterfaceImpôtsDao.py):
Die Schnittstelle definiert drei Methoden:
- [get_admindata]: ist die Methode, die die Steuerklasse-Tabelle abruft. Beachten Sie, dass keine Informationen darüber gegeben werden, wie diese Daten zu beschaffen sind. Später werden sie zunächst in einer Textdatei und dann in einer Datenbank zu finden sein. Es liegt an den Klassen, die die Schnittstelle implementieren, sich an die jeweilige Datenspeichermethode anzupassen. Wir werden daher eine Klasse haben, um die Steuerklassen aus einer Textdatei abzurufen, und eine andere, um sie aus einer Datenbank abzurufen. Beide werden die Methode [get_admindata] implementieren;
- [get_taxpayers_data]: ist die Methode, die Steuerzahlerdaten abruft. Auch hier geben wir nicht an, wo diese zu finden sind. Wir behandeln nur den Fall, in dem sie in einer Textdatei vorliegen;
- [write_taxpayers_results]: ist die Methode, die die Ergebnisse der Steuerberechnung speichert. Wir geben nicht an, wo. Wir behandeln nur den Fall, in dem die Ergebnisse in einer Textdatei gespeichert werden. Der Parameter [taxpayers_results] ist die Liste der zu speichernden Ergebnisse;
15.1.2.2. Die Klasse [AbstractImpôtsDao]
Die [dao]-Schicht wird durch zwei Klassen implementiert:
- Eine ruft die Daten (Steuerzahler, Ergebnisse, Steuerklassen) aus Textdateien ab;
- die andere ruft Daten (Steuerzahler, Ergebnisse) aus Textdateien und Steuerklassen aus einer Datenbank ab;
Die beiden Klassen unterscheiden sich lediglich in der Art und Weise, wie sie mit Steuerklassen umgehen. Steuerzahlerdaten und Steuerberechnungsergebnisse werden auf die gleiche Weise verwaltet. Aus diesem Grund werden wir sie in einer übergeordneten Klasse [AbstractImpôtsDao] verwalten. Die spezifische Handhabung der Steuerklassen wird in zwei untergeordneten Klassen verwaltet:
- Die Klasse [ImpôtsDaoWithAdminDataInJsonFile] ruft die Steuerklassen aus einer Textdatei im JSON-Format ab;
- Die Klasse [ImpôtsDaoWithAdminDataInDatabase] ruft die Steuerklassen aus einer Datenbank ab;
Die übergeordnete Klasse [AbstractImpôtsDao] sieht wie folgt aus:
- Zeile 13: Die Klasse [AbstractImpôtsDao] implementiert die Schnittstelle [InterfaceImpôtsDao]. Daher enthält sie die drei Methoden dieser Schnittstelle:
- [get_taxpayers_data]: Zeile 31;
- [write_taxpayers_results]: Zeile 35;
- [get_admindata]: Zeile 40. Diese Methode wird von der Klasse [AbstractImpôtsDao] nicht implementiert, daher wird sie als abstrakt deklariert (Zeile 39);
- Zeile 16: Der Konstruktor erhält ein [config]-Wörterbuch, das die folgenden Informationen enthält:
- [taxpayersFilename]: der Name der Textdatei, die die Steuerzahlerdaten enthält;
- [resultsFilename]: der Name der Textdatei, in der die Ergebnisse gespeichert werden;
- [errorsFilename]: der Name der Textdatei, in der die bei der Verarbeitung der Datei [taxpayersFilename] aufgetretenen Fehler aufgelistet sind;
Die Methode [get_taxpayers_data] lautet wie folgt:
- Zeile 4: Die Steuerzahlerdaten (verheiratet, Kinder, Gehalt) werden in eine Liste von Objekten vom Typ [TaxPayer] aufgenommen;
- Zeilen 8–9: Wir öffnen die Steuerzahler-Textdatei zum Lesen. Ihr Inhalt hat das folgende Format:
Im Vergleich zu früheren Versionen:
- Jede Zeile in der Datei [taxpayersFilename] beginnt mit der Steueridentifikationsnummer, einer einzelnen Zahl;
- Kommentare und Leerzeilen sind zulässig;
- wir werden Fehler behandeln. Daher müssen die Zeilen 17, 19 und 21 als ungültig markiert werden. Fehler werden in einer separaten Datei protokolliert;
Fahren wir mit der Durchsicht des Codes fort:
- Zeile 4: Die Daten aus der Textdatei werden in die Liste [taxPayersData] übertragen;
- Zeilen 14–31: Die Steuerzahlerdatei wird Zeile für Zeile gelesen;
- Zeile 14: Das Ende der Datei ist erreicht, wenn eine leere Zeile gelesen wird (nichts – nicht einmal das Zeilenendezeichen \r\n);
- Zeile 20: Leere Zeilen und Kommentare werden ignoriert. Eine Zeile ist ein Kommentar, wenn nach dem Entfernen der Leerzeichen vor und nach dem Text das erste Zeichen das #-Zeichen ist;
- Zeile 24: Eine gültige Zeile besteht aus vier durch Kommas getrennten Feldern. Diese werden abgerufen. Die Zuweisung von Daten zu einem vierelementigen Tupel schlägt fehl, wenn nicht genau vier Datenpunkte zugewiesen sind;
- Zeile 25: Wenn eines der vier abgerufenen Felder [id, married, children, salary] ungültig ist, löst die Methode [BaseEntity.fromdict] eine [MyException]-Ausnahme aus;
- Zeilen 25–26: Ein [TaxPayer]-Objekt wird der Liste [taxpayers_data] mit Steuerzahlern hinzugefügt;
- Zeilen 27–29: Alle Fehler werden in einer Liste [errors] gesammelt. Diese Liste wurde in Zeile 6 erstellt;
- Zeilen 33–36: Die Liste der aufgetretenen Fehler wird in der Textdatei [errorsFilename] gespeichert. Es gibt zwei Arten von Fehlern:
- Eine Zeile wies nicht die richtige Anzahl der erwarteten Felder auf;
- die Informationen in der Zeile waren falsch und es konnte kein [TaxPayer]-Objekt erstellt werden;
- Zeilen 39–41: Jeder Fehler (BaseException) wird abgefangen und weitergeleitet, indem er in einen Typ [TaxPayerError] verpackt wird;
- Zeilen 42–45: In allen Fällen, ob erfolgreich oder nicht, wird die Steuerzahler-Textdatei geschlossen, falls sie geöffnet war;
Die Methode [write_taxpayers_results] muss eine JSON-Datei im folgenden Format erzeugen:
[
{
"id": 1,
"marié": "oui",
"enfants": 2,
"salaire": 55555,
"impôt": 2814,
"surcôte": 0,
"taux": 0.14,
"décôte": 0,
"réduction": 0
},
{
"id": 2,
"marié": "oui",
"enfants": 2,
"salaire": 50000,
"impôt": 1384,
"surcôte": 0,
"taux": 0.14,
"décôte": 384,
"réduction": 347
},
{
"id": 3,
"marié": "oui",
"enfants": 3,
"salaire": 50000,
"impôt": 0,
"surcôte": 0,
"taux": 0.14,
"décôte": 720,
"réduction": 0
},
…
]
Die Methode [write_taxpayers_results] lautet wie folgt:
- Zeile 2: Die Methode erhält eine Liste von Steuerzahlern [taxpayers], die sie im JSON-Format in der Textdatei [self.taxpayers_results_filename] speichern muss;
- Zeile 10: Erstellung der UTF-8-Ergebnisdatei;
- Zeile 12: Hier führen wir die Funktion [map] ein, deren Syntax hier [map (function, list1)] lautet. Die Funktion [function] wird auf jedes Element von [list1] angewendet und erzeugt ein neues Element, das in die Liste [list2] aufgenommen wird. Schließlich gilt für jedes i:
liste2[i]=fonction(liste1[i])
Hier ist [list1] die Liste [taxPayers], eine Liste von Objekten vom Typ [TaxPayer]. Die Funktion [function] wird hier als sogenannte [lambda]-Funktion ausgedrückt, die die auf ein Element [taxpayer] der Liste [taxpayers] angewendete Transformation beschreibt: Jedes [taxpayer]-Element wird durch sein Wörterbuch [taxpayer.asdict()] ersetzt. Schließlich ist die resultierende Liste [list2] die Liste der Wörterbücher der Elemente in der Liste [taxpayers];
- Zeile 12: Das von der Funktion [map] zurückgegebene Ergebnis ist nicht die Liste [list2], sondern ein Objekt vom Typ [map]. Um [list2] zu erhalten, müssen Sie den Ausdruck [list(mapping)] verwenden (Zeile 14);
- Zeile 14: Die Liste [list2] wird im JSON-Format in der Datei [self.taxpayers_results_filename] gespeichert;
- Zeilen 15–17: Jede Art von Ausnahme wird abgefangen und in ein [ImpôtsError] verpackt, bevor sie erneut ausgelöst wird (Zeile 17);
- Zeilen 19–21: In jedem Fall, ob erfolgreich oder nicht, wird die Ergebnisdatei geschlossen, falls sie geöffnet war;
15.1.2.3. Klasse [ImpôtsDaoWithAdminDataInJsonFile]
Die Klasse [ImpôtsDaoWithAdminDataInJsonFile] wird von der Klasse [AbstractImpôtsDao] abgeleitet und die Methode [getAdminData] implementieren, die ihre übergeordnete Klasse nicht implementiert hat. Sie wird Steuerverwaltungsdaten aus einer JSON-Datei abrufen:
{
"limites": [9964, 27519, 73779, 156244, 0],
"coeffr": [0, 0.14, 0.3, 0.41, 0.45],
"coeffn": [0, 1394.96, 5798, 13913.69, 20163.45],
"plafond_qf_demi_part": 1551,
"plafond_revenus_celibataire_pour_reduction": 21037,
"plafond_revenus_couple_pour_reduction": 42074,
"valeur_reduc_demi_part": 3797,
"plafond_decote_celibataire": 1196,
"plafond_decote_couple": 1970,
"plafond_impot_couple_pour_decote": 2627,
"plafond_impot_celibataire_pour_decote": 1595,
"abattement_dixpourcent_max": 12502,
"abattement_dixpourcent_min": 437
}
Die Klasse [ImpôtsDaoWithAdminDataInJsonFile] sieht wie folgt aus:
- Zeile 11: Die Klasse [ImpôtsDaoWithAdminDataInJsonFile] erbt von der Klasse [AbstractImpôtsDao]. Als solche implementiert sie die Schnittstelle [InterfaceImpôtsDao];
- Zeile 13: Der Konstruktor erhält als Parameter ein Wörterbuch, das die Informationen aus den Zeilen 14–17 enthält;
- Zeile 20: Die übergeordnete Klasse wird initialisiert;
- Zeile 24: Die JSON-Datei mit den Daten der Steuerbehörde wird geöffnet;
- Zeile 25: Die UTF-8-Datei mit den Daten der Steuerbehörde wird geöffnet;
- Zeile 27: Der Inhalt der Datei wird gelesen und in das Objekt [self.admindata] vom Typ [AdminData] gespeichert. Die Schlüssel in der JSON-Datei müssen mit den für ein [AdminData]-Objekt akzeptierten Eigenschaften übereinstimmen; andernfalls löst die Methode [fromdict] eine Ausnahme aus;
- Zeilen 28–30: Ausnahmebehandlung. Alle möglicherweise auftretenden Ausnahmen werden in einen Typ [ImpôtsError] verpackt, bevor sie erneut ausgelöst werden;
- Zeilen 32–34: Die Datei wird geschlossen, falls sie geöffnet wurde;
- Zeilen 42–43: Implementierung der Methode [get_admindata] der Schnittstelle [InterfaceImpôtsDao];
15.1.3. Die [business]-Schicht

15.1.3.1. Die Schnittstelle [InterfaceImpôtsMétier]
Die Schnittstelle für die [Geschäfts-]Ebene sieht wie folgt aus:
- Die Schnittstelle [BusinessTaxInterface] definiert eine einzige Methode:
- Zeile 12: Die Methode [calculate_tax] berechnet die Steuer für einen einzelnen Steuerzahler [taxpayer]. [admindata] ist das [AdminData]-Objekt, das die Daten der Steuerverwaltung kapselt;
- Zeile 12: Die Methode [calculate_tax] gibt kein Ergebnis zurück. Die erhaltenen Daten (Steuer, Zuschlag, Rabatt, Ermäßigung, Steuersatz) werden in den Parameter [taxpayer] aufgenommen: Vor dem Aufruf sind diese Attribute leer; nach dem Aufruf sind sie initialisiert;
15.1.3.2. Die Klasse [BusinessTaxes]
Die Klasse [ImpôtsMétier] implementiert die Schnittstelle [InterfaceImpôtsMétier] wie folgt:

Die Klassenmethoden stammen aus dem Modul [impôts_module_02] im Abschnitt |Das Modul [impots.v02.modules.impôts_module_02]|. Wir haben die Methodenparameter auf nur zwei beschränkt:
- taxpayer(id, married, children, salary, tax, discount, surcharge, reduction, rate): das Objekt, das einen Steuerzahler und dessen Steuern repräsentiert;
- admindata: das Objekt, das die Daten der Steuerverwaltung kapselt;
Wir veranschaulichen die vorgenommenen Änderungen anhand einer Methode;
- Zeile 3: Die Methode [calculate_tax] ist die einzige Methode in der Schnittstelle [InterfaceImpôtsMétier]. Sie nimmt zwei Parameter entgegen:
- [tapPayer]: der Steuerzahler, für den die Steuer berechnet wird;
- [admindata]: das Objekt, das die Daten der Steuerverwaltung enthält;
- Die Ergebnisse der Berechnung werden im Parameter [taxpayer] gekapselt (Zeilen 40–50). Der Inhalt dieses Objekts ist daher vor und nach dem Aufruf der Methode nicht identisch;
15.1.4. Tests für die Schichten [dao] und [business]

- [TestDaoMétier] ist die UnitTest-Klasse zum Testen der [dao]- und [business]-Schichten;
- [config] ist die Testkonfigurationsdatei;
Die [config]-Konfiguration lautet wie folgt:
- Zeilen 4–23: Wir konfigurieren den Python-Pfad für die Tests;
- Zeilen 32–41: Instanziieren der Schichten [dao] und [business]. Speichern ihrer Referenzen im Wörterbuch [config];
- Zeile 44: Dieses Wörterbuch zurückgeben;
Die Testklasse [TestDaoMétier] sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
Kommentare
- Zeile 11: Die Testklasse erweitert die Klasse [unittest.TestCase];
- Zeilen 13–19: In einem UnitTest wird die Methode [setUp] vor jeder der [test_]-Methoden ausgeführt;
- Zeile 16: Die Konfiguration aus dem zuvor besprochenen [config]-Skript wird abgerufen;
- Zeile 18: Eine Referenz auf die [business]-Schicht wird gespeichert;
- Zeile 19: Wir fordern das [AdminData]-Objekt – das Daten der Steuerverwaltung kapselt – von der [DAO]-Schicht an und speichern es;
- Zeilen 21–173: 11 Tests, deren Ergebnisse auf der offiziellen Steuer-Website 2019 |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm| verifiziert wurden;
- Zeilen 21–33: Alle Tests wurden anhand derselben Vorlage erstellt;
- Zeile 22: Import der Klasse [TaxPayer];
- Zeile 24: Der zu testende Steuerzahler;
- Zeile 25: erwartete Ergebnisse;
- Zeile 26: Erstellung des [TaxPayer]-Objekts des Steuerzahlers;
- Zeile 27: Berechnung der Steuer. Das Ergebnis befindet sich in [taxpayer];
- Zeilen 29–33: Überprüfung der erzielten Ergebnisse;
- Zeile 29: Wir überprüfen den Steuerbetrag auf den nächsten Euro. Tests haben tatsächlich gezeigt, dass die vom Algorithmus in diesem Dokument erzielten Ergebnisse um bis zu 1 Euro von den offiziellen Zahlen abweichen können;
Die Durchführung der Tests liefert folgende Ergebnisse:

15.1.5. Hauptskript

Das Hauptskript wird durch das folgende [config]-Skript konfiguriert:
Sie ähnelt derjenigen, die zum Testen der [Business]- und [DAO]-Schichten verwendet wird.
Das Hauptskript [main.py] sieht wie folgt aus:
Anmerkungen
- Zeilen 2–4: Wir rufen die Anwendungskonfiguration ab. Wir wissen auch, dass der Python-Pfad der Anwendung erstellt wurde;
- Zeilen 9–11: Wir rufen Referenzen auf die [Business]- und [DAO]-Schichten ab;
- Zeile 15: Wir rufen Daten von der Steuerverwaltung ab;
- Zeile 17: Wir rufen die Liste der Steuerzahler ab, für die Steuern berechnet werden müssen;
- Zeilen 19–20: Ist diese Liste leer, wird eine Ausnahme ausgelöst;
- Zeilen 22–25: Berechnung der Steuer für die verschiedenen [taxpayer]-Objekte unter Verwendung der [business]-Schicht;
- Zeile 27: [taxpayers] ist nun eine Liste von [TaxPayer]-Objekten, denen Werte für die Attribute (tax, discount, surcharge, reduction, rate) zugewiesen wurden. Diese Liste wird in eine JSON-Datei geschrieben;
- Zeilen 28–30: Erfassen Sie mögliche Fehler;
- Zeilen 31–33: werden in allen Fällen ausgeführt;
Die Ausführung des Skripts liefert die gleichen Ergebnisse wie in früheren Versionen. Die Steuerzahler-Fehlerdatei war eine neue Funktion in dieser Version. Nach Ausführung des [main]-Skripts sieht dessen Inhalt wie folgt aus:
Analyse du fichier C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v04\main\01/../../data/input/taxpayersdata.txt
Ligne 17, not enough values to unpack (expected 4, got 2)
Ligne 19, too many values to unpack (expected 4)
Ligne 21, MyException[1, L'identifiant d'une entité <class 'TaxPayer.TaxPayer'> doit être un entier >=0]
Die fehlerhaften Zeilen lauteten wie folgt:
15.2. Version 4 – Anwendung 2
In dieser Version gibt der Benutzer die Liste der Steuerzahler über die Tastatur ein. Die Anwendungsarchitektur sieht wie folgt aus:

Es kommt ein neues Modul hinzu: die [ui]-Schicht (User Interface), die mit dem Benutzer interagiert. Diese Schicht verfügt über eine Schnittstelle und wird durch eine Klasse implementiert.

15.2.1. Die Schnittstelle [InterfaceImpôtsUi]
Die Schnittstelle [InterfaceImpôtsUi] wird nur eine Methode haben, nämlich die in den Zeilen 8–10. Die Schnittstelle wird hier mit einer Konsolenanwendung implementiert, könnte aber auch mit einer grafischen Benutzeroberfläche implementiert werden. Die an die Methode [run] übergebenen Parameter wären in beiden Implementierungen nicht identisch. Um dieses Problem zu umgehen, wird üblicherweise wie folgt vorgegangen:
- keine Parameter an die Methode [run] zu übergeben (oder nur die Mindestanzahl an Parametern zu übergeben);
- Parameter an den Konstruktor der Klasse übergeben, die die Schnittstelle implementiert. Diese können sich von einer Implementierung zur anderen unterscheiden. Diese Parameter werden als Klassenattribute gespeichert;
- sicherstellen, dass die [run]-Methode diese Klassenattribute verwendet (self.x);
Diese Methode ermöglicht eine sehr allgemeine Schnittstelle, die durch die Parameter der Konstruktoren jeder Implementierungsklasse festgelegt wird. Diese Methode wurde bereits für die modulare Version Nr. 1 verwendet.
15.2.2. Die Klasse [ImpôtsConsole]
Die Klasse [ImpôtsConsole] implementiert die Schnittstelle [InterfaceImpôtsUi] wie folgt:
- Zeile 9: Die Klasse [TaxConsole] implementiert die Schnittstelle [TaxUiInterface];
- Zeile 11: Der Klassenkonstruktor erhält einen Parameter, das [config]-Wörterbuch, das die Anwendungskonfiguration enthält;
- Zeile 13: Es werden Daten von der Steuerbehörde abgerufen, um die Steuer zu berechnen;
- Zeile 14: Eine Referenz auf die [business]-Schicht wird gespeichert;
- Zeile 16: Implementierung der Methode [run] der Schnittstelle;
- Zeilen 19–53: Benutzerinteraktion. Dabei
- die Abfrage von drei Informationen beim Steuerzahler (Familienstand, Kinder, Gehalt);
- Berechnung seiner Steuer;
- Anzeige des Ergebnisses;
- der Dialog endet, wenn der Benutzer auf die erste Frage mit * antwortet;
- Zeilen 20–27: Das Programm fragt, ob der Steuerzahler verheiratet ist, und überprüft die Gültigkeit der Antwort;
- Zeilen 29–31: Wenn der Benutzer die Frage mit „*“ beantwortet hat, endet der Dialog;
- Zeilen 32–39: Der Steuerzahler wird gefragt, wie viele Kinder er hat, und die Gültigkeit der Antwort wird überprüft;
- Zeilen 40–47: Das Jahreseinkommen des Steuerzahlers wird abgefragt und die Gültigkeit der Antwort überprüft;
- Zeilen 48–50: Anhand dieser Informationen berechnet die [Business]-Schicht die Steuer des Steuerzahlers;
- Zeile 52: Der Steuerbetrag wird angezeigt;
15.2.3. Das Hauptskript
Das Hauptskript [main] wird durch die folgende [config]-Datei konfiguriert:
Das Hauptskript lautet wie folgt (main.py):
- Zeilen 1–4: Abrufen der Anwendungskonfiguration;
- Zeile 10: Ruft eine Referenz auf die [ui]-Ebene ab;
- Zeilen 12–21: Die Codestruktur entspricht der der vorherigen Anwendung: Der Code ist in einen try/catch-Block eingeschlossen, um mögliche Ausnahmen abzufangen;
- Zeile 15: Wir weisen die [ui]-Schicht an, die Ausführung zu starten: Die Benutzerinteraktion beginnt dann;
- Zeilen 16–18: Abfangen möglicher Ausnahmen;
Hier ist ein Beispiel für die Ausführung:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/v04/main/02/main.py
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : oui
Nombre d'enfants : 3
Salaire annuel : 200000
Impôt du contribuable = {"id": 0, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : *
Travail terminé...
Process finished with exit code 0