← Python/Eigen typen
Les 6 van 8
MODULE 07 · 06◷ 28 min

Gelijke waarden en hetzelfde object onderscheiden

Na deze les kun je
  • Waardegelijkheid en identiteit uit elkaar houden.
  • Voor passende objecten alle relevante velden vergelijken.
  • Andere typen met NotImplemented aan het vergelijkingsprotocol overlaten.

Twee opgeslagen coördinaten kunnen allebei het punt (2, 5) voorstellen. Het zijn toch twee afzonderlijke objecten. Soms wil je hun plaats vergelijken, en soms wil je juist weten of twee variabelen naar precies hetzelfde object verwijzen.

Waarde en identiteit apart bekijken

Identiteit gaat over hetzelfde object en wordt met is onderzocht. Waardegelijkheid gaat over de betekenis van de opgeslagen gegevens en wordt gewoonlijk met == gevraagd. Die twee vragen kunnen bij twee gelijke punten verschillende antwoorden geven.

Een dataclass krijgt standaard een vergelijking van haar velden. In het voorbeeld staan twee afzonderlijke instanties met dezelfde gegevens. Voorspel zowel de gelijkheidsvergelijking als de identiteitscontrole.

Gelijke dataclasswaarden blijven twee instanties
from dataclasses import dataclass

@dataclass
class Coordinate:
    row: int
    column: int

first = Coordinate(2, 5)
second = Coordinate(2, 5)
print(first == second)
print(first is second)
alias = first
print(alias is first)
Verwachte uitvoer
True
False
True

Een variabele extra aan hetzelfde object koppelen verandert zijn identiteit niet. Een nieuw object met dezelfde veldwaarden blijft wel een afzonderlijke instantie. Het verschil wordt belangrijk zodra een van die objecten later kan veranderen.

Zelf de betekenis van gelijkheid kiezen

Bij een gewone klasse kun je __eq__ schrijven om de gelijkheidsvergelijking vorm te geven. De methode ontvangt self en other. Voor twee passende punten vergelijk je zowel de x- als de y-waarde.

Als je alleen de eerste coördinaat vergelijkt, worden bijvoorbeeld (2, 5) en (2, 9) ten onrechte gelijk. Als je alleen self is other teruggeeft, blijven twee afzonderlijke punten met dezelfde coördinaten ongelijk. Kies dus de velden die samen de betekenis van de waarde bepalen.

Een ander soort object

Voor een vergelijking die jouw methode niet ondersteunt, is er de speciale waarde NotImplemented. Geef die terug wanneer het andere object volgens jouw contract niet bij deze vergelijking hoort. Je werpt hiervoor geen NotImplementedError en maakt er geen tekst van.

Met deze waarde laat je Python ruimte om de andere kant van de vergelijking te proberen. Wanneer beide kanten een gelijkheidsvergelijking niet ondersteunen, kan Python voor == terugvallen op identiteit. NotImplemented is daarom een antwoord binnen het vergelijkingsprotocol, geen gewoon False voor ieder onbekend object.

Een eigen gelijkheidsbetekenis en onbekend type
class Coordinate:
    def __init__(self, row, column):
        self.row = row
        self.column = column

    def __eq__(self, other):
        if type(other) is not type(self):
            return NotImplemented
        return self.row == other.row and self.column == other.column

first = Coordinate(2, 5)
print(first == Coordinate(2, 5))
print(first == Coordinate(2, 9))
print(first.__eq__((2, 5)) is NotImplemented)
print(first == (2, 5))
Verwachte uitvoer
True
False
True
False

In deze les vergelijken we puntwaarden van hetzelfde concrete type. Een tuple met dezelfde twee getallen is daarmee niet automatisch een Point. Dat voorkomt dat een toevallig vergelijkbare gegevensvorm stilzwijgend dezelfde domeinbetekenis krijgt.

Veelgemaakte fout: ieder attribuut zomaar lezen

Zonder typecontrole meteen other.x lezen kan bij een ander soort object een AttributeError veroorzaken. De vraag of twee waarden gelijk zijn hoort dan onbedoeld op een foutpad te eindigen. Bepaal eerst of jouw vergelijking dit andere type ondersteunt.

Bij passende objecten vergelijk je de echte waarden. Bij een ander type geef je de afgesproken speciale waarde terug. De controles onderzoeken ook die directe uitkomst, zodat een ongeschikte vergelijking niet alleen door toevallige voorbeeldgegevens verborgen blijft.

Gelijkheid gebruiken

Een lijst kan met in zoeken naar een gelijk element. Na een passende __eq__ hoeft het gezochte punt daarom niet dezelfde instantie te zijn als het punt in de lijst. De domeinbetekenis zit in de waarden die jouw klasse vergelijkt.

Een kopie met dezelfde coördinaten kan eveneens gelijk zijn en toch een eigen identiteit hebben. Zo kan een functie een zelfstandige puntwaarde teruggeven zonder de verwijzing van de aanroeper opnieuw te gebruiken. De nieuwe en de oude instantie mogen elkaar daarna niet ongemerkt veranderen.

AAN DE SLAG 0/4
1

Beide coördinaten vergelijken

Vul Point.__eq__(other) aan: twee objecten van hetzelfde concrete type zijn gelijk wanneer x én y gelijk zijn. Geef daarvoor echte booleans terug; gelijkheid hangt niet af van dezelfde identiteit.

2

Een ander type aan Python overlaten

Laat Point.__eq__ bij een ander concreet type NotImplemented teruggeven. Dat geldt ook voor een verdere subklasse. De gewone == met een tuple of None moet daarmee False worden, zonder exception.

3

Op gelijke waarden zoeken

Maak contains_point(points, target) die een echte boolean geeft voor de aanwezigheid van een gelijk punt. Een afzonderlijke instantie met dezelfde coördinaten moet gevonden worden; verander de lijst niet.

4

Een zelfstandige gelijke waarde maken

Maak copy_point(point) die een nieuwe Point met dezelfde x en y teruggeeft. De kopie is gelijk maar niet hetzelfde object; een latere wijziging aan haar coördinaten mag het origineel niet veranderen.

Onthoud dit

  • is vraagt naar identiteit; == vraagt naar de gekozen gelijkheidsbetekenis.
  • Een eigen __eq__ vergelijkt alle relevante velden van passende objecten.
  • NotImplemented laat een niet-ondersteunde vergelijking verder afhandelen door Python.

Hierna bouw je objecten die andere objecten bevatten en kies je bewust wat zij delen.

main.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.