Hoofdstuk 7 · oefening
Objecten weergeven en vergelijken
Maak objecten inspecteerbaar met repr()
Je hebt inmiddels veel attributen afgedrukt. Misschien heb je nog geen object afgedrukt:
Iets zoals <__main__.Question object at 0x7f3c...>. Het is niet fout. De standaardweergave identificeert de klasse en bevat een identiteitsmarkering, in plaats van de attributen te tonen waarmee jij de vraag zou herkennen.
Het is ook onbruikbaar voor wat je meestal wilt: ontdekken wat je voor je hebt.
De weergaveoefeningen gebruiken kleinere versies van de quizklassen, zodat je je op representaties kunt richten. De gegeven Question maakt de puntenwaarde nu alleen-lezen beschikbaar, net als de vraagtekst en het antwoord. De latere lessen over gelijkheid en hashing gebruiken die stabiele publieke toestand.
Waar dit lastig wordt
Het probleem is het duidelijkst binnen een collectie:
Twee objecten, maar geen van beide weergaven vertelt welke vraagtekst erin zit. Dit is de uitvoer die je krijgt van een debugger, een variabelenpaneel, een mislukte assertie en een haastige print om twee uur 's nachts. In elk van die situaties moest je weten om welke vraag het ging.
Het object leren zichzelf te beschrijven
Definieer een methode met de naam __repr__ die een string teruggeeft:
Beide regels zijn nu leesbaar, ook die binnen een lijst, en dat is de regel die ertoe doet.
Via methoden met twee underscores aan elke kant vraagt Python een object om mee te doen aan een taalvoorziening. Je hebt er al één geschreven: __init__ laat een object deelnemen aan het aanmaken. __repr__ laat het deelnemen aan de weergave. Normaal gebruik je de taalbewerking, zoals een klasse aanroepen of repr(object) gebruiken, en roept Python de speciale methode voor je aan.
Hoe een goede repr eruitziet
Het is de moeite waard om de conventie precies te volgen, omdat die voordeel oplevert:
f"Question({self.prompt!r}, {self.answer!r}, {self.points!r})"
Streef, waar dat praktisch is, naar een string die lijkt op de code waarmee je een gelijkwaardig object zou maken. Voor een object dat geschiedenis of externe bronnen bevat, is een beknopt, duidelijk benoemd overzicht eerlijker dan doen alsof de tekst een uitvoerbare constructoraanroep is.
Dat is wat !r doet. Binnen een f-string vraagt !r om de repr van een waarde in plaats van de gewone tekstvorm. Dat is het verschil tussen deze twee:
Zonder !r verschijnt een string zonder aanhalingstekens en kun je de string 3 niet onderscheiden van het getal 3. Daarmee zijn de aanhalingstekens zichtbaar. Gebruik in een __repr__ altijd !r voor waarden die strings kunnen zijn.
Waarom gebruikt de __repr__ hierboven {self.points!r} in plaats van {self.points}?
repr in de hulpmiddelen die je al gebruikt
Zodra een klasse een __repr__ heeft, verschijnt die zonder extra werk overal:
De ingebouwde functie repr() roept die aan. Containers roepen die aan voor hun inhoud. In de browser-REPL naast deze cursus toont het invoeren van de naam van een variabele de repr. Daarom is een object met een goede repr zoveel makkelijker te verkennen.
Merk op dat de REPL en containers repr gebruiken, terwijl print op het object zelf een andere methode verkiest zodra een klasse die definieert. Dat is de volgende les.
Houd het licht en altijd werkend
Twee gewoonten die je nu al kunt aanleren.
Een __repr__ wordt juist uitgevoerd in situaties waarin er al iets misgaat: debuggers, foutmeldingen, logs. Die mag nooit traag werk doen en nooit een exceptie opgooien. Een repr die faalt terwijl je een fout onderzoekt, verbergt het probleem dat je probeerde te vinden.
De methode moet ook eerlijk zijn over wat ze toont. Twee afzonderlijke objecten die waarden voorstellen mogen redelijkerwijs dezelfde repr hebben. Als identiteit voor een bepaalde diagnose belangrijk is, voeg dan alleen een stabiele identificatie toe als het model die echt heeft; verzin geen constructorargument dat de klasse niet accepteert.
Opdracht
Geef beide klassen een nuttige, eerlijke __repr__.
Question moet Question('What does len(...) return?', '3', 2) opleveren, waarbij elke waarde wordt getoond zoals je die in code zou schrijven.
QuizAttempt moet QuizAttempt(learner='Mina', response_count=2) opleveren. Dat is nadrukkelijk een overzicht, geen aanroep die de constructor met één argument zou kunnen uitvoeren. De methode kan niet elk antwoord tonen zonder onleesbaar te worden en meldt daarom het aantal.
Geen van beide __repr__-methoden mag een exceptie opgooien, ook niet voor een poging zonder antwoorden. Voer het programma uit en inspecteer beide lijsten: de ene bevat vragen en de laatste bevat een poging zonder antwoorden.