35

How do I check if two instances of a

class FooBar(object):
    __init__(self, param):
        self.param = param
        self.param_2 = self.function_2(param)
        self.param_3 = self.function_3()

are identical? By identical I mean they have the same values in all of their variables.

a = FooBar(param)
b = FooBar(param)

I thought of

if a == b:
    print "a and b are identical"!

Will this do it without side effects?

The background for my question is unit testing. I want to achieve something like:

self.failUnlessEqual(self.my_object.a_function(), another_object)
Serp C
  • 864
  • 1
  • 10
  • 24
Aufwind
  • 25,310
  • 38
  • 109
  • 154

6 Answers6

58

If you want the == to work, then implement the __eq__ method in your class to perform the rich comparison.

If all you want to do is compare the equality of all attributes, you can do that succinctly by comparison of __dict__ in each object:

class MyClass:

    def __eq__(self, other) : 
        return self.__dict__ == other.__dict__
AJ.
  • 27,586
  • 18
  • 84
  • 94
  • 16
    Note that the `__dict__`-comparing `__eq__` may return `True` even if `other` is a totally different class, as long as it has identical attributes. It's contrieved, I know. Insert an `if self.__class__ != other.__class__: return False` first if you wish to guard against it nontheless. – Lauritz V. Thaulow Jun 21 '11 at 11:06
  • @AJ: If I have implemented `__eq__` in my class and I had a list of instances `L_1 = [obj_1, obj_2, obj_3]` and another list `L_2 = [obj_1, obj_2, obj_3]`, will `L_1 == L_2` work as expected? – Aufwind Jun 21 '11 at 17:06
  • In short, yes. Note that they don't have to be the **same objects**, as you've defined the two example lists to include. They could be two lists, each containing different objects and still be equal IFF the objects in the lists are compared to be equal **in sequence**. Note that sequence does count. – AJ. Jun 21 '11 at 17:19
  • This does not work if one of the properties contain array as member – Code Pope Mar 23 '23 at 14:34
6

For an arbitrary object, the == operator will only return true if the two objects are the same object (i.e. if they refer to the same address in memory).

To get more 'bespoke' behaviour, you'll want to override the rich comparison operators, in this case specifically __eq__. Try adding this to your class:

def __eq__(self, other):
    if self.param == other.param \
    and self.param_2 == other.param_2 \
    and self.param_3 == other.param_3:
        return True
    else:
        return False

(the comparison of all params could be neatened up here, but I've left them in for clarity).

Note that if the parameters are themselves objects you've defined, those objects will have to define __eq__ in a similar way for this to work.

Another point to note is that if you try to compare a FooBar object with another type of object in the way I've done above, python will try to access the param, param_2 and param_3 attributes of the other type of object which will throw an AttributeError. You'll probably want to check the object you're comparing with is an instance of FooBar with isinstance(other, FooBar) first. This is not done by default as there may be situations where you would like to return True for comparison between different types.

See AJ's answer for a tidier way to simply compare all parameters that also shouldn't throw an attribute error.

For more information on the rich comparison see the python docs.

actionshrimp
  • 5,219
  • 3
  • 23
  • 26
6

For python 3.7 onwards you can also use dataclass to check exactly what you want very easily. For example:

from dataclasses import dataclass

@dataclass
class FooBar:
    param: str
    param2: float
    param3: int

a = Foobar("test_text",2.0,3)
b = Foobar("test_text",2.0,3)

print(a==b)

would return True

KZiovas
  • 3,491
  • 3
  • 26
  • 47
1

According to Learning Python by Lutz, the "==" operator tests value equivalence, comparing all nested objects recursively. The "is" operator tests whether two objects are the same object, i.e. of the same address in memory (same pointer value). Except for cache/reuse of small integers and simple strings, two objects such as x = [1,2] and y = [1,2] are equal "==" in value, but y "is" x returns false. Same true with two floats x = 3.567 and y = 3.567. This means their addresses are different, or in other words, hex(id(x)) != hex(id(y)).

For class object, we have to override the method __eq__() to make two class A objects like x = A(1,[2,3]) and y = A(1,[2,3]) "==" in content. By default, class object "==" resorts to comparing id only and id(x) != id(y) in this case, so x != y. In summary, if x "is" y, then x == y, but opposite is not true.

Leon Chang
  • 669
  • 8
  • 12
1

If this is something you want to use in your tests where you just want to verify fields of simple object to be equal, look at compare from testfixtures:

from testfixtures import compare

compare(a, b)
Altair7852
  • 1,226
  • 1
  • 14
  • 23
0

To avoid the possibility of adding or removing attributes to the model and forgetting to do the appropriate changes to your __eq__ function, you can define it as follows.

def __eq__(self, other):
    if self.__class__ == other.__class__:
        fields = [field.name for field in self._meta.fields]
        for field in fields:
            if not getattr(self, field) == getattr(other, field):
                return False
        return True
    else:
        raise TypeError('Comparing object is not of the same type.')

In this way, all the object attributes are compared. Now you can check for attribute equality either with object.__eq__(other) or object == other.