Kapitel 8 · Übung
Dataclasses und Wertobjekte
Wiederholten Klassencode erkennen
Hier ist die vollständige und korrekte Klasse Points vom Ende von Kapitel 7:
class Points:
def __init__(self, amount):
if amount <= 0:
raise ValueError("points must be greater than zero")
self._amount = amount
@property
def amount(self):
return self._amount
def __repr__(self):
return f"Points({self.amount!r})"
def __eq__(self, other):
if not isinstance(other, Points):
return NotImplemented
return self.amount == other.amount
def __hash__(self):
return hash(self.amount)
def __lt__(self, other):
if not isinstance(other, Points):
return NotImplemented
return self.amount < other.amount
Mehr als zwanzig Zeilen, um zu sagen: „Ein Points ist ein positiver, schreibgeschützter Betrag.“
Manche Zeilen sind vorhersehbare Mechanik: das deklarierte Feld speichern, anzeigen und vergleichen. Positivität, Schreibschutz, Hashing und Ordnung sind eigene Entwurfszusagen. Eine Dataclass kann den vorhersehbaren Teil erzeugen. Diese Zusagen nimmt sie dir aber nicht ab.
Dieselbe Form, zweimal
Sieh dir an, was passiert, wenn ein Programm mehrere kleine Wertklassen hat:
class Points:
def __init__(self, amount):
self.amount = amount
def __repr__(self):
return f"Points({self.amount!r})"
def __eq__(self, other):
if not isinstance(other, Points):
return NotImplemented
return self.amount == other.amount
class Duration:
def __init__(self, seconds):
self.seconds = seconds
def __repr__(self):
return f"Duration({self.seconds!r})"
def __eq__(self, other):
if not isinstance(other, Duration):
return NotImplemented
return self.seconds == other.seconds
Lies beide nebeneinander. Ändere drei Namen, und es ist dieselbe Klasse. Bei Duration wurde nichts neu überlegt, nur getippt oder, wahrscheinlicher, kopiert und angepasst.
Warum kopierter Code schiefgeht
Der Aufwand liegt nicht im Tippen. Die Kopien entwickeln sich auseinander.
Zwei Ergebnisse mit unterschiedlichen Gesamtpunktzahlen, die als gleich gemeldet werden. Die repr-Darstellung unterstützt die falsche Aussage, weil auch sie total auslässt.
Jemand hat total zu __init__ hinzugefügt und die beiden Methoden darunter nicht angepasst. Das ist weniger Unachtsamkeit als eine Frage der Anzahl: Drei Stellen mussten geändert werden, zwei wurden übersehen. Bei acht Attributen und sechs Klassen stellt sich nicht mehr die Frage, ob das irgendwann passiert.
Was ist in der obigen Klasse Result schiefgegangen?
Was „Boilerplate“ bedeutet
Solcher Code hat einen Namen: Boilerplate. Damit ist nicht schlechter Code gemeint, sondern Code, dessen Inhalt vollständig aus etwas folgt, das du bereits festgelegt hast. Kannst du aus der Attributliste jede Zeile vorhersagen, vermitteln diese Zeilen keine neue Information. Sie existieren nur, weil die Sprache sie verlangt.
Die richtige Antwort auf Boilerplate ist nicht, schneller zu tippen. Frage stattdessen, ob etwas diesen Code erzeugen kann, sodass du nichts mehr aufeinander abstimmen musst.
Nicht alles davon ist Boilerplate
Unterscheide genau, welche Teile dazugehören. Die nächste Lektion erzeugt genau diese und nicht mehr:
| Durch die Attribute bestimmt | Bewusst entschieden |
|---|---|
__init__ weist jedes zu | Welche Attribute existieren |
__repr__ listet sie auf | Ob die Klasse überhaupt ein Wert ist |
__eq__ vergleicht sie | Ob Punkte positiv sein müssen |
| Die Feldreihenfolge in diesen Methoden | Was is_correct bedeutet |
Die linke Spalte können die Standardeinstellungen einer Dataclass für dich schreiben. Die rechte Spalte ist dein Entwurf. Kein Werkzeug entscheidet sie. Hashing, Ordnung, Einfrieren und Validierung verlangen ausdrückliche Entscheidungen, die später im Kapitel folgen.
Die Übung lässt dich das Auseinanderlaufen in einem kleinen Programm finden, bevor du die Funktion kennenlernst, die es verhindert. Wenn du genau weißt, wozu die Mechanik dient, wirkt die nächste Lektion nicht wie Magie.
Aufgabe
Drei Klassen wurden hier von Hand geschrieben, und jede passt nicht mehr vollständig zu ihren eigenen Attributen.
Repariere alle drei so, dass jedes in __init__ zugewiesene Attribut auch in __repr__ und __eq__ vorkommt:
Resultspeichert drei Werte und vergleicht zwei.Durationspeichert zwei Werte und zeigt einen.AttemptSummaryist eine Momentaufnahme als Wertobjekt, die drei Werte speichert. Ihr__eq__fehlt vollständig.
Folge dem vorhandenen repr-Format der Klassen: ClassName(value, value), wobei jeder Wert so geschrieben wird, wie er im Code erscheinen würde.
Das Programm am Ende gibt Paare aus, die nicht gleich sein sollten. Wenn du fertig bist, sollen die drei Vergleichszeilen False melden. Die letzten beiden Zeilen sollen in den Objektdarstellungen jeden gespeicherten Wert zeigen.