Dataclasses und Wertobjekte · Übung
Eine erste Dataclass erstellen
Python kann die Boilerplate aus der vorherigen Lektion schreiben. Hier ist die vollständige Klasse Result:
Fünf Zeilen. __init__, __repr__ und __eq__ existieren alle, und alle drei kennen jedes Attribut, weil sie aus derselben Liste erzeugt wurden.
Der Fehler aus der vorherigen Lektion ist hier nicht nur behoben. Er kann auf diese Weise gar nicht entstehen. Die drei Stellen können nicht auseinanderlaufen, denn es gibt nur eine Stelle.
Die Zeile am Anfang
from dataclasses import dataclass ist das erste import in diesem Kurs und verdient deshalb eine Erklärung.
Python bringt eine große Sammlung fertiger Werkzeuge mit, die Standardbibliothek. Sie stehen nicht alle automatisch bereit. Alles in jedes Programm zu laden wäre verschwenderisch und würde deinen Namensraum mit Hunderten Namen füllen. Eine import-Zeile sagt, welche Werkzeuge du möchtest. Diese hier fordert das Werkzeug dataclass aus der Sammlung dataclasses an. Anschließend ist dataclass ein gewöhnlicher Name, den du verwenden kannst.
Mehr brauchst du für dieses Kapitel nicht. Kapitel 10 behandelt Importe ausführlich, einschließlich eigener Module. Das ist ein umfangreiches Thema. Betrachte die Zeile bis dahin als Teil der Schreibweise für Dataclasses.
Was die einzelnen Zeilen tun
@dataclass
class Result:
learner: str
score: int
total: int
@dataclass über einer Klasse hat dieselbe Form wie @property über einer Methode. Es übergibt die Klasse an ein Werkzeug, das sie untersucht und Methoden ergänzt.
Im Inneren ist learner: str eine Annotation: der Name eines Attributs und der erwartete Typ seines Werts. Annotationen hast du in Kapitel 2 an Funktionen kennengelernt. Python setzt sie nicht durch, Werkzeuge verwenden sie für Prüfungen. Hier gilt dasselbe, mit einer Ergänzung: @dataclass liest diese Zeilen, um herauszufinden, welche Attribute existieren. Es ist die einzige Funktion in diesem Kurs, bei der eine Annotation das Verhalten des Programms verändert.
Die Typen werden also weiterhin nicht erzwungen:
Python erzeugt das Objekt ohne Einwand. Die Annotation hält eine Absicht fest und erlaubt einem Typprüfer, dich zu warnen. Muss ein Wert tatsächlich eine Zahl sein, ist das eine Aufgabe aus Kapitel 5: Validiere ihn.
Was tut score: int innerhalb einer Dataclass?
Was du bekommst und was nicht
@dataclass erzeugt __init__, __repr__ und __eq__. Das ist der Standard. Es lohnt sich, seine Grenzen genau zu kennen:
Kein __hash__, aus dem in Kapitel 7 erklärten Grund: Eine Dataclass ist standardmäßig veränderbar, daher wäre ihr Hashing unsicher. Lektion 5 zeigt die Einstellung, die eine Dataclass zugleich unveränderlich und hashbar macht.
Ohne weitere Vorgaben gibt es auch kein __str__, keine automatische Validierung und keine Ordnung.
Eigene Methoden gehören weiterhin dazu
Eine Dataclass ist eine gewöhnliche Klasse. Alles, was du weißt, gilt weiterhin:
Das erzeugte __init__ ruft nach dem Zuweisen der Felder __post_init__ auf. An diesem vorgesehenen Erweiterungspunkt hält eine Dataclass die Konstruktionsregeln aus Kapitel 5 ein. Erzeugte __init__, __repr__ und __eq__; selbst geschriebene Validierung, is_correct und __str__. Bei diesen Methoden bleibt eine eigene Definition erhalten, statt durch eine erzeugte Version ersetzt zu werden.
Genau darum geht es: Die Funktion entfernt die Zeilen, die du ohnehin geschrieben hättest, und lässt die Entwurfsentscheidungen an ihrem bisherigen Platz.
Aufgabe
Ersetze die von Hand geschriebene Mechanik durch erzeugte Mechanik und behalte alles, was eine echte Entscheidung war.
Result ist bereits umgestellt, und der Import ist vorhanden. Verwende es als Muster, um Duration und Question zu Dataclasses zu machen. Beide verlieren ihre __init__, __repr__ und __eq__ und erhalten für jeden gehaltenen Wert eine annotierte Attributzeile. Behalte die Attributnamen und ihre Reihenfolge exakt bei, denn davon hängen die Konstruktoraufrufe am Ende ab.
Behalte jede nicht erzeugte Methode: Duration.total_seconds, Question.is_correct und Question.__str__. Stelle die Konstruktionsregeln von Question in __post_init__ wieder her: Fragetext und erwartete Antwort dürfen nicht leer sein, und die Punkte müssen größer als null sein.
Ergänze kein __hash__. Die nächsten Lektionen erklären, warum das nicht einfach ein Versehen ist.
Verhalten und eigene Darstellung sollen gleich bleiben. Die erzeugte repr-Darstellung enthält jetzt Feldnamen, etwa Question(prompt=..., answer=..., points=...). Diese Zeile wird sich daher ändern.