Document Actions

Not equal not not equal. By default.

Or: always implement __ne__ alongside __eq__

by rbp posted at 2007-09-10 16:39 last modified 2008-01-23 18:24

At our last Dojo session (Danilo has blogged about our Dojo, in Portuguese - I wonder when he'll switch to English?), the Kata being presented required knowing whether two instances of the same class were equal, based on some internal semantics. As in "object1 == object2". The coders went on, dutifully, and implemented the __eq__ method, which, in Python, is called whenever such a comparison is requested. Except that it's not enough. Or, perhaps more dangerously, it seems to be enough, but isn't.

You see, Python's operator overloading defines which methods are called when each specific operator is used. So, when you want to add two objects of a class you wrote, "a + b" will be converted into "a.__add__(b)", and all you have to do is define an __add__ method to your class that handles addition according to your class's semantics (there's a bit more to it, but that'll suffice for now).

Back to equality: using the "==" operator will result in a call to the "__eq__" method; if there is no such method in your class (nor was it inherited), the default is to compare the object's address in memory, or "id" (that is to say, equality comparison is true if, and only if, the objects are precisely the same). So far so good:

class Default:
pass

class Rich:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value

The Default class just uses default comparison, and the Rich one checks for equality using a value passed to objects at the time of their creation:

>>> default_1 = Default()
>>> default_2 = Default()
>>> default_1 == default_2 # Different ids
False
>>> default_1 == default_1
True
>>> rich_1 = Rich(1)
>>> rich_2 = Rich(2)
>>> also_1 = Rich(1)
>>> rich_1 == rich_2 # Different values
False
>>> rich_1 == also_1 # Same values...
True
>>> rich_1 is also_1 # ... but different ids
False

Looks ok, and our Dojo coders got as far as that. Now we can tell if two objects are equal, but can we tell if they're different?

>>> rich_1 != rich_2
True

Fine. But what if they are supposed to be equal?

>>> rich_1 != also_1
True

That's the problem. When you define __eq__, you overload the "==" operator, but "!=" remains as the default, which is to check whether both objects are not located at the same address in memory! Objects rich_1 and also_1 have different addresses and thus compare as different when we look at them through !='s eyes, even though they're equal through =='s. So now we have objects that are equal and different at the same time!

>>> rich_1 == also_1, rich_1 != also_1
(True, True)

Fortunately, the solution is simple: we just have to define the __ne__ method to overload "!=". We don't even have to put the logic in there again, we can just make sure it's the opposite of what we're considering "being equal" to mean:

class Richer:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not (self == other)

Easy. Now the objects work as expected (at least as far as equality comparisons are concerned):

>>> richer_1 = Richer(1)
>>> alsoer_1 = Richer(1)
>>> richer_1 == alsoer_1
True
>>> richer_1 != alsoer_1
False

There.

Now, someone asked me why this behaviour (not __eq__) is not the default for __ne__.
I really have no idea. It seems to work even for the default case of __eq__ checking for the id. The Python documentation says: "There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false", but I can't see a practical reason for it since, for the vast majority of uses, x==y should imply that x!=y is false. I don't think "explicit is better than implicit" applies, since there is already an implicit assumption for !=, if you don't do anything explicit about that.

Anyone?


[isnomore.net]
software
blog
completely different things
Google Reader shared items
Google Reader shared items rss feed
bê do érre
ali ckel
cybershark
Recent entries
pyconbrasil[3][1] rbp 2007-10-06
Fun for the whole family! rbp 2007-09-27
Not equal not not equal. By default. rbp 2007-09-10
pyconbrasil[3].pictures rbp 2007-09-09
pyconbrasil[3][0] rbp 2007-09-08
Recent comments
Re:pyconbrasil[3][1] Danilo Sato 2007-10-06
Re:pyconbrasil[3][1] rbp 2007-10-06
Re:Fun for the whole family! rbp 2007-10-02
Re:Fun for the whole family! Anonymous User 2007-10-02
Re:You vicious, heartless bastard! Anonymous User 2007-08-15
Categories
python (13)
meta (8)
english (19)
portugues (0)
OLPC (1)
spam (4)
agile (2)
nnebs (3)
coreblog (2)
community (6)
pyconbrasil3 (5)
About this blog
rbp's random ramblings (and alliterations, as always)
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: