0%

Objecten weergeven en vergelijken · oefening

Definieer waardegelijkheid

Question is een waardeobject, dus twee vragen met dezelfde inhoud horen gelijk te zijn. Dat vertel je Python met één methode.

== definiëren

Try it

== vergelijkt nu de inhoud. is vergelijkt nog steeds objecten en zal dat altijd blijven doen: identiteit is niet iets wat een klasse opnieuw mag definiëren.

Alles wat op == is gebouwd verbetert tegelijk:

Try it

De controle op aanwezigheid uit de vorige les werkt nu, net als .index(), .remove() en het vergelijken van twee lijsten.

Vergelijken met iets dat geen vraag is

Die __eq__ bevat een fout, en het is de fout die iedereen de eerste keer maakt:

first == "What does len('cat') return?"

AttributeError, omdat een string geen .prompt heeft. Twee dingen vergelijken hoort nooit een exceptie op te gooien. == wordt gebruikt door in, lijstvergelijkingen, .index(), .remove() en testframeworks, vaak op collecties waarvan je de inhoud niet zelf hebt gekozen. Sorteren is anders: dat gebruikt volgordevergelijkingen of een sleutelfunctie.

De oplossing is eerst het type controleren:

Try it

isinstance(other, Question) vraagt of other een Question is. Zo niet, dan geeft de methode NotImplemented terug.

Dat lijkt een fout te zijn en verdient een alinea uitleg. NotImplemented is een speciale waarde die betekent: ‘ik weet niet hoe ik mezelf daarmee moet vergelijken’. Python behandelt dat als een antwoord, niet als een resultaat: het vraagt vervolgens het andere object of dat het weet. Als geen van beide het weet, valt Python terug op het vergelijken van identiteit, wat False oplevert. Rechtstreeks False teruggeven zou hier ook werken, maar zou ongemerkt voorkomen dat een andere klasse zichzelf ooit gelijk aan een vraag kan verklaren. NotImplemented teruggeven is de gewoonte om aan te leren.

Waarom controleert __eq__ isinstance(other, Question) voordat het attributen vergelijkt?

Kiezen wat meetelt

__eq__ is een ontwerpbeslissing, geen formaliteit. Je legt vast welke delen van een object bepalen wat het is.

Denk aan een Response die ook registreert wanneer het antwoord is gegeven:

class Response:
    def __init__(self, question, given, answered_at):
        ...

Moeten twee antwoorden op dezelfde vraag, met dezelfde tekst en één seconde ertussen, gelijk zijn? Er is geen universeel antwoord. Als Response een waarde is die beschrijft wat er is gezegd, is het tijdstip bijkomstig en moet het worden weggelaten. Als het een registratie van een gebeurtenis is, hoort het tijdstip bij wat er gebeurde, en twee gebeurtenissen zijn nooit dezelfde gebeurtenis.

Twee regels die altijd gelden:

Geef de voorkeur aan stabiele waardetoestand. Veranderbare objecten zoals lijsten kunnen terecht nu gelijk en later ongelijk zijn. Maar voor een klasse die zich als waarde moet gedragen, maakt alleen-lezen publieke toestand gelijkheid makkelijker te begrijpen. De volgende les legt de strengere regel uit waaraan een object moet voldoen voordat het mag worden gehasht en als dictionarysleutel gebruikt.

Maak de repr nuttig om gelijkheid te begrijpen. Door de vergeleken velden te tonen, wordt duidelijker waarom twee waarden verschillen. Een overzicht kan details weglaten, dus overeenkomende reprs bewijzen op zichzelf niet dat objecten gelijk zijn.

De gewoonte

Van gelijkheid wordt verwacht dat die zich als gelijkheid gedraagt: een object is gelijk aan zichzelf, a == b komt overeen met b == a, en als a == b en b == c, dan geldt a == c. De gewone strings en getallen in dit voorbeeld vergelijken behoudt die verwachtingen. Een string aan beide kanten normaliseren voordat je vergelijkt kan dat ook doen. Een regel zoals ‘bedragen binnen één punt zijn gelijk’ doet dat niet: 1 komt overeen met 2 en 2 met 3, maar 1 niet met 3. Houd zo’n controle op nabijheid in een afzonderlijke methode.

De oefening voegt gelijkheid toe aan twee alleen-lezen waarden: Question en Points, een positieve hoeveelheid. De op identiteit gebaseerde QuizAttempt krijgt geen gelijkheidsmethode.

Opdracht

Geef waardegelijkheid aan de twee klassen die die verdienen en aan geen andere.

Question is gelijk als de vraagtekst, het antwoord en de punten allemaal overeenkomen. Points is gelijk als de hoeveelheid overeenkomt.

Beide moeten NotImplemented teruggeven bij een vergelijking met iets dat niet hun eigen klasse is, zodat question == "text" False meldt in plaats van een exceptie op te gooien.

Laat QuizAttempt met rust. Twee pogingen zijn alleen dezelfde poging als ze hetzelfde object zijn. Dat is wat Python al doet, dus daar een __eq__ toevoegen zou echt verkeerd zijn.

Voer het programma uit. De functie find_duplicate onderaan moet de herhaalde vraag melden, wat die nu helemaal niet kan.