0%

Kapitel 7 · Übung

Objekte darstellen und vergleichen

Objekte mit repr() verständlich anzeigen

Inzwischen hast du viele Attribute ausgegeben. Vielleicht aber noch kein Objekt selbst:

Try it

Etwas wie <__main__.Question object at 0x7f3c...>. Das ist nicht falsch. Die Standarddarstellung nennt die Klasse und enthält ein Identitätsmerkmal, statt die Attribute anzuzeigen, an denen du die Frage erkennen würdest.

Für das, was du meistens wissen möchtest, ist sie allerdings nutzlos: herauszufinden, was du vor dir hast.

Die Übungen zur Darstellung verwenden kleinere Versionen der Quizklassen, damit du dich auf die Darstellung konzentrieren kannst. Das vorgegebene Question bietet seine Punktzahl nun ebenso wie Fragetext und erwartete Antwort nur lesbar an. Die späteren Lektionen zu Gleichheit und Hashing verwenden diesen stabilen öffentlichen Zustand.

Wo das besonders stört

Am deutlichsten zeigt sich das Problem innerhalb einer Sammlung:

Try it

Zwei Objekte, doch keine Darstellung verrät, welchen Fragetext eines enthält. Diese Ausgabe begegnet dir im Debugger, in einer Variablenansicht, bei einer fehlgeschlagenen Zusicherung und bei einem eiligen print um zwei Uhr nachts. In jedem dieser Fälle musstest du wissen, um welche Frage es sich handelt.

Dem Objekt beibringen, sich selbst zu beschreiben

Definiere eine Methode namens __repr__, die einen String zurückgibt:

Try it

Beide Zeilen sind jetzt lesbar, auch die innerhalb einer Liste. Gerade diese ist wichtig.

Über Methoden mit zwei Unterstrichen an beiden Enden bittet Python ein Objekt, an einer Sprachfunktion teilzunehmen. Eine hast du bereits geschrieben: __init__ beteiligt das Objekt an seiner Erzeugung. __repr__ beteiligt es an seiner Darstellung. Normalerweise verwendest du die Sprachoperation, etwa den Aufruf einer Klasse oder repr(object), und Python ruft die spezielle Methode für dich auf.

Wie eine gute repr-Darstellung aussieht

Es lohnt sich, die Konvention genau einzuhalten:

f"Question({self.prompt!r}, {self.answer!r}, {self.points!r})"

Wenn es praktikabel ist, strebe einen String an, der wie der Code aussieht, mit dem du ein gleichwertiges Objekt erzeugen würdest. Enthält ein Objekt einen Verlauf oder externe Ressourcen, ist eine knappe, klar gekennzeichnete Zusammenfassung ehrlicher als Text, der nur vorgibt, ein ausführbarer Konstruktoraufruf zu sein.

Dafür ist !r da. Innerhalb eines f-Strings fordert !r die repr-Darstellung eines Werts statt seiner gewöhnlichen Textform an. Das ist der Unterschied zwischen diesen beiden:

Try it

Ohne !r erscheint ein String ohne Anführungszeichen. Du kannst den String 3 nicht von der Zahl 3 unterscheiden. Damit sind die Anführungszeichen sichtbar. Verwende in einem __repr__ für Werte, die Strings sein könnten, immer !r.

Warum verwendet das obige __repr__ den Ausdruck {self.points!r} statt {self.points}?

repr in deinen bisherigen Werkzeugen

Sobald eine Klasse ein __repr__ hat, erscheint es ohne weitere Arbeit überall:

Try it

Die eingebaute Funktion repr() ruft es auf. Container rufen es für ihre Inhalte auf. Wenn du im Browser-REPL neben diesem Kurs den Namen einer Variablen eingibst, zeigt es deren repr-Darstellung. Deshalb lässt sich ein Objekt mit einer guten Darstellung so viel leichter erkunden.

Beachte: REPL und Container verwenden repr. print für das Objekt selbst bevorzugt dagegen eine andere Methode, sobald die Klasse sie definiert. Darum geht es in der nächsten Lektion.

Schnell und zuverlässig halten

Zwei Gewohnheiten lohnen sich von Anfang an.

Ein __repr__ läuft genau dann, wenn bereits etwas schiefgeht: in Debuggern, Fehlermeldungen und Logs. Es sollte niemals langsame Arbeit erledigen und niemals eine Ausnahme auslösen. Eine repr-Darstellung, die bei der Fehlerdiagnose scheitert, verdeckt das eigentliche Problem.

Sie sollte außerdem ehrlich zeigen, was sie zeigt. Zwei verschiedene wertartige Objekte dürfen durchaus dieselbe repr-Darstellung haben. Ist ihre Identität für eine bestimmte Diagnose wichtig, ergänze nur dann eine stabile Kennung, wenn das Modell tatsächlich eine besitzt. Erfinde kein Konstruktorargument, das die Klasse nicht entgegennehmen kann.

Aufgabe

Gib beiden Klassen ein nützliches, ehrliches __repr__.

Question soll Question('What does len(...) return?', '3', 2) liefern, wobei jeder Wert so erscheint, wie er im Code geschrieben würde.

QuizAttempt soll QuizAttempt(learner='Mina', response_count=2) liefern. Das ist ausdrücklich eine Zusammenfassung, kein Aufruf, den sein Konstruktor mit einem Argument ausführen könnte. Alle Antworten aufzuführen würde die Darstellung unlesbar machen, daher meldet sie stattdessen die Anzahl.

Keines der beiden __repr__ darf eine Ausnahme auslösen, auch nicht bei einem Versuch ohne Antworten. Führe das Programm aus und betrachte beide Listen: Eine enthält Fragen, die letzte einen Versuch ohne Antworten.