ActiveState Code

Recipe 510403: Mixins for equality, rich comparisons and hashing


Many comparable or hashable classes calculate all comparison and hash results from the same simple calculation. The mixins in this recipe allow such classes to define a single __key__() method from which all the __eq__(), __lt__(), __hash__(), etc. methods are automatically defined.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class KeyedEqualityMixin(object):
   def __eq__(self, other):
       return self.__key__() == other.__key__()
   def __ne__(self, other):
       return self.__key__() != other.__key__()

class KeyedComparisonMixin(KeyedEqualityMixin):
   def __lt__(self, other):
       return self.__key__() < other.__key__()
   def __le__(self, other):
       return self.__key__() <= other.__key__()
   def __gt__(self, other):
       return self.__key__() > other.__key__()
   def __ge__(self, other):
       return self.__key__() >= other.__key__()

class KeyedHashingMixin(KeyedEqualityMixin):
    def __hash__(self):
        return hash(self.__key__())


# =============================
# And at the interactive prompt
# =============================

>>> class C(object):
...     def __init__(self, x):
...         self.x = x
... 
>>> x, y, z = C(1), C(1), C(2)
>>> x == y, y < z, hash(x) == hash(y)
(False, False, False)
>>> class C(KeyedEqualityMixin):
...     def __init__(self, x):
...         self.x = x
...     def __key__(self):
...         return self.x
...        
>>> x, y, z = C(1), C(1), C(2)
>>> x == y, y < z, hash(x) == hash(y)
(True, False, False)
>>> class C(KeyedComparisonMixin):
...     def __init__(self, x):
...         self.x = x
...     def __key__(self):
...         return self.x
... 
>>> x, y, z = C(1), C(1), C(2)
>>> x == y, y < z, hash(x) == hash(y)
(True, True, False)
>>> class C(KeyedHashingMixin):
...     def __init__(self, x):
...         self.x = x
...     def __key__(self):
...         return self.x
... 
>>> x, y, z = C(1), C(1), C(2)
>>> x == y, y < z, hash(x) == hash(y)
(True, False, True)

Discussion

This approach is generally cleaner than writing a __cmp__() method where you'd often have to specify the attribute(s) twice, e.g.:: <pre> def __cmp__(self, other): return cmp(self.x, other.x)</pre> With the __key__() approach, you only need to name the attribute once.

If you've been relying on __cmp__ methods to handle all your rich comparisons, you should probably start using an approach like the one in this recipe because the __cmp__ method will no longer exist in Python 3000.

Sign in to comment