views:

1509

answers:

5

When writing custom classes it is often important to allow equivalence by means of the == and != operators. In Python, this is made possible by implementing the __eq__ and __ne__ special methods, respectively. The easiest way I've found to do this is the following method:

class Foo:
    def __init__(self, item):
        self.item = item

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

Do you know of more elegant means of doing this? Do you know of any particular disadvantages to using the above method of comparing __dict__s?

Note: A bit of clarification--when __eq__ and __ne__ are undefined, you'll find this behavior:

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False

That is, a == b evaluates to False because it really runs a is b, a test of identity (i.e., "Is a the same object as b?").

When __eq__ and __ne__ are defined, you'll find this behavior (which is the one we're after):

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
+3  A: 

You don't have to override both __eq__ and __ne__ you can override only __cmp__ but this will make an implication on the result of ==, !==, < , > and so on.

is tests for object identity. This means a is b will be True in the case when a and b both hold the reference to the same object. In python you always hold a reference to an object in a variable not the actual object, so essentially for a is b to be true the objects in them should be located in the same memory location. How and most importantly why would you go about overriding this behaviour?

Edit: I didn't know __cmp__ was removed from python 3 so avoid it.

Vasil
Because sometimes you have a different definition of equality for your objects.
Ed Swangren
the is operator gives you the interpreters answer to object identity, but you are still free to express you view on equality by overriding __cmp__
Vasil
In Python 3, "The cmp() function is gone, and the __cmp__() special method is no longer supported." http://is.gd/aeGv
gotgenes
+1  A: 

I think that the two terms you're looking for are equality (==) and identity (is). For example:

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True       <-- a and b have values which are equal
>>> a is b
False      <-- a and b are not the same list object
too much php
Maybe, except that one can create a class that only compares the first two items in two lists, and if those items are equal, it evaluates to True. This is equivalence, I think, not equality. Perfectly valid in __eq__, still.
gotgenes
I do agree, however, that "is" is a test of identity.
gotgenes
+7  A: 

The way you describe is the way I've always done it. Since it's totally generic, you can always break that functionality out into a mixin class and inherit it in classes where you want that functionality.

class CommonEqualityMixin(object):

    def __eq__(self, other):
        return (isinstance(other, self.__class__)
            and self.__dict__ == other.__dict__)

    def __ne__(self, other):
        return not self.__eq__(other)

class Foo(CommonEqualityMixin):

    def __init__(self, item):
        self.item = item
cdleary
+1: Strategy pattern to allow easy replacement in subclasses.
S.Lott
isinstance sucks. Why check it? Why not just self.__dict__ == other.__dict__?
nosklo
@nosklo: I don't understand.. what if two objects from completely unrelated classes happen to have the same attributes?
max
@max nosklo makes a good point. Consider the default behavior when sub-classing the built in objects. The `==` operator does not care if you compare a built-in to a sub-class of the built-in.
gotgenes
I thought nokslo suggested skipping isinstance. In that case you no longer know if `other` is of a subclass of `self.__class__`.
max
@max: yeah, but it doesn't matter if it is a subclass or not. It is irrelevant information. Consider what will happen if it is not a subclass.
nosklo
@nosklo: if it's not subclass, but it just happens by accident to have same attributes as `self` (both keys and values), `__eq__` might evaluate to `True`, even though it's meaningless. Do I miss anything?
max
@nosklo: Yeah, maybe `hasattr(other, '__dict__') and self.__dict__ == other.__dict__` would be better in the general case. I guess I just prefer a stricter notion of equality, given the option.
cdleary
+8  A: 

You need to be careful with inheritance:

>>> class Foo:
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

>>> class Bar(Foo):pass

>>> b = Bar()
>>> f = Foo()
>>> f == b
True
>>> b == f
False

Check types more strictly, like this:

def __eq__(self, other):
    if type(other) is type(self):
        return self.__dict__ == other.__dict__
    return False

Besides that, your approach will work fine, that's what special methods are there for.

Algorias
Very good point and missed by the accepted answer!
Kamil Kisiel
This is a good point. I suppose it's worth noting that sub-classing built in types still allows for equality either direction, and so checking that it's the same type may even be undesirable.
gotgenes
A: 

The 'is' test will test for identity using the builtin 'id()' function which essentially returns the memory address of the object and therefore isn't overloadable.

However in the case of testing the equality of a class you probably want to be a little bit more strict about your tests and only compare the data attributes in your class:

import types

class ComparesNicely(object):

    def __eq__(self, other):
        for key, value in self.__dict__.iteritems():
            if (isinstance(value, types.FunctionType) or 
                    key.startswith("__")):
                continue

            if key not in other.__dict__:
                return False

            if other.__dict__[key] != value:
                return False

         return True

This code will only compare non function data members of your class as well as skipping anything private which is generally what you want. In the case of Plain Old Python Objects I have a base class which implements __init__, __str__, __repr__ and __eq__ so my POPO objects don't carry the burden of all that extra (and in most cases identical) logic.

mcrute
Bit nitpicky, but 'is' tests using id() only if you haven't defined your own is_() member function (2.3+). [http://docs.python.org/library/operator.html]
spenthil
I assume by "override" you actually mean monkey-patching the operator module. In this case your statement is not entirely accurate. The operators module is provided for convenience and overriding those methods does not affect the behavior of the "is" operator. A comparison using "is" always uses the id() of an object for the comparison, this behavior can not be overridden. Also an is_ member function has no effect on the comparison.
mcrute
mcrute - I spoke too soon (and incorrectly), you are absolutely right.
spenthil