In hoofdstuk 211 beschreef ik dat objecten een alias van elkaar kunnen zijn, maar dat je ook echte kopieën van objecten kunt maken. Wat gebeurt er als ik probeer objecten met elkaar te vergelijken?

class Punt:
    def __init__( self, x=0.0, y=0.0 ):
        self.x = x
        self.y = y
    def __repr__( self ):
        return "({}, {})".format( self.x, self.y )

p1 = Punt( 3, 4 )
p2 = Punt( 3, 4 )
p3 = p1
print( p1 is p2 )
print( p1 is p3 )
print( p1 == p2 )
print( p1 == p3 )

Het gereserveerde woord is wordt gebruikt om identiteiten met elkaar te vergelijken. Omdat p3 een alias is van p1, retourneert p1 is p3 True, terwijl p1 is p2 False retourneert.

Het ==-teken zou een waarde-vergelijking moeten doen. Omdat p1 en p2 refereren aan hetzelfde punt in de 2-dimensionale ruimte, zou het fijn zijn als p1 == p2 True zou retourneren (want dat is wat je zou verwachten van een waarde-vergelijking). Maar dat gebeurt niet. Vreemd is dat niet, aangezien Python niet kan weten hoe je de waarde van Punten moet vergelijken (althans niet zonder verdere specificatie). Daarom doet == de enige vergelijking die Python standaard kent, namelijk de vergelijking van identiteiten, dus dezelfde vergelijking als is doet. Je kunt Python echter vertellen hoe de waarde van twee punten vergeleken kan worden via de speciale methode __eq__():

class Punt:
    def __init__( self, x=0.0, y=0.0 ):
        self.x = x
        self.y = y
    def __repr__( self ):
        return "({}, {})".format( self.x, self.y )
    def __eq__( self, p ):
        return self.x == p.x and self.y == p.y

p1 = Punt( 3, 4 )
p2 = Punt( 3, 4 )
p3 = p1
print( p1 is p2 )
print( p1 is p3 )
print( p1 == p2 )
print( p1 == p3 )

De __eq__() methode vertelt Python in dit geval wat gedaan moet worden als twee objecten van het type Punt met elkaar vergeleken worden middels ==. Het retourneert True als de x en y coördinaten gelijk zijn, en anders False. In dit voorbeeld heeft er “operator overloading” plaatsgevonden voor de vergelijkingsoperator == door de __eq__() methode in te vullen.

Je kunt ook de andere vergelijkingsoperatoren “overloaden,” dus de \=!, >, >=, <, en <=:

Als je __eq__() invult maar __ne__() niet, dan retourneert __ne__() automatisch het omgekeerde van wat __eq__() retourneert. Geen van de andere methodes heeft een dergelijke automatische interpretatie.

Je bent niet beperkt tot het vergelijken van objecten van dezelfde class. Bijvoorbeeld, ik kan een class Quaternion maken die een quaternion implementeert, die ik zou willen vergelijken met een integer of een float. Dat kan:

class Quaternion:
    def __init__( self, a, b, c, d ):
        self.a = a
        self.b = b
        self.c = c
        self.d = d
    def __repr__( self ):
        return "({},{}i,{}j,{}k)".format( self.a, self.b, 
            self.c, self.d )
    def __eq__( self, n ):
        if isinstance( n, int ) or isinstance( n, float ):
            if self.a == n and self.b == 0 and \
                self.c == 0 and self.d == 0:
                return True
            else:
                return False
        elif isinstance( n, Quaternion ):
            if self.a == n.a and self.b == n.b and \
                self.c == n.c and self.d == n.d:
                return True
            else:
                return False
        return NotImplemented

c1 = Quaternion( 1, 2, 3, 4 )
c2 = Quaternion( 1, 2, 3, 4 )
c3 = Quaternion( 3, 0, 0, 0 )
if c1 == c2:
    print( c1, "==", c2 )
else:
    print( c1, "!=", c2 )
if c1 == c3:
    print( c1, "==", c3 )
else:
    print( c1, "!=", c3 )
if c3 == 1:
    print( c3, "==", 1 )
else:
    print( c3, "!=", 1 )
if c3 == 3:
    print( c3, "==", 3 )
else:
    print( c3, "!=", 3 )
if c3 == 3.0:
    print( c3, "==", 3.0 )
else:
    print( c3, "!=", 3.0 )
if c3 == "3":
    print( c3, "== \"3\"" )
else:
    print( c3, "!= \"3\"" ) 
if 3 == c3:
    print( 3, "==", c3 )
else:
    print( 3, "!=", c3 )

De implementatie van de __eq__() methode in de code hierboven test eerst of een vergelijking gemaakt wordt met een Quaternion, een integer, of een float. Als het één van deze data types is, dan wordt de vergelijking uitgevoerd, en het resultaat geretourneerd als True of False. Als het geen van de data types is, wordt NotImplemented geretourneerd. NotImplemented is een speciale waarde die aangeeft dat de vergelijking geen zinvolle uitkomst geeft. Hoewel de __ne__() methode het resultaat van de __eq__() methode inverteert, kan hij niet NotImplemented inverteren.

In de code hierboven zou je iets moeten opvallen aan de laatste vergelijking. De vergelijking 3 == c3 wordt uitgevoerd. Gewoonlijk wordt de vergelijkingsoperator uitgevoerd voor de linker-operand, wat in dit geval wil zeggen dat de vergelijking die gedefinieerd is voor de integer 3 wordt uitgevoerd, met c3 als argument. Maar voor integers houdt de __eq__() methode geen rekening met Quaternionen (de makers van Python, die verantwoordelijk zijn voor de integer class, kunnen immers niet weten dat ik een Quaternion class zal bouwen), en dus zal de vergelijking NotImplemented retourneren. Als dat echter gebeurt, inverteert Python de operanden, zodat in dit geval de vergelijking c3 == 3 wordt uitgevoerd. Dit leidt dan wel tot een resultaat, aangezien voor een Quaternion de vergelijking met een integer gedefinieerd is. Hetzelfde zal gebeuren voor de \=! operator. En iets vergelijkbaars gebeurt voor de andere vergelijkingsoperatoren, maar als daar de operanden worden verwisseld, wordt ook een < omgewisseld met een >, en een <= met een >=, zoals je zou verwachten.

In hoofdstuk 212 werd een class Rechthoek gedefinieerd. Voeg aan deze class operatoren toe die de gelijkheid van rechthoeken testen (twee rechthoeken zijn gelijk als ze exact dezelfde vorm hebben, zelfs als ze niet dezelfde plek in de ruimte innemen), en voeg ook operatoren toe die testen of een rechthoek kleiner of groter dan een andere rechthoek is (op basis van de oppervlaktes). Test de nieuwe operatoren. Ik wil hierbij opmerken dat ik een beetje twijfel of dit wel acceptabele definities zijn voor vergelijkingen van rechthoeken, maar als oefening kan het ermee door.

Er is nog een speciaal soort vergelijking die ik wil bespreken, en dat is het testen of een object True of False is. Veel objecten worden als False beschouwd in speciale omstandigheden; bijvoorbeeld, een lege list wordt als False geëvalueerd. Ik heb dit kort besproken in hoofdstuk 73.

buffer = []
if buffer:
    print( buffer )
else:
    print( "buffer is empty" )

Je kunt je eigen evaluatie van een object definiëren die gebruikt wordt als het object optreedt als conditie. Dit gaat via de __bool__() methode.

__bool__() wordt aangeroepen als een object als conditie behandeld wordt. Hij retourneert True of False. Als __bool__() niet geïmplementeerd is, wordt in plaats ervan __len__() aangeroepen (die bespreek ik hieronder), en zal er False geretourneerd worden als __len__() nul retourneert. Als noch __bool__() noch __len__() geïmplementeerd is, zal het object altijd als True beschouwd worden in een conditie.