How to implement __hash__ for an object with multiple comparable properties

Viewed 152

I have a class called Transaction, which contains multiple attributes. If any of these attributes match, then i want those transactions to be treated as duplicate transactions and hence do not want to store duplicates in a set.

class Transaction:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __eq__(self, other):
        if not isinstance(other, Transaction):
            return NotImplemented
        return self.a == other.a or self.b == other.b

    def __hash__(self):
        # TODO

I learnt that it is important to implement both __eq__ as well as __hash__ if we want to avoid duplicates while inserting in a set. Also, if A == B, then their hashes should also match as per the contract.

How can i implement __hash__ in this case, so that if i try to insert a transaction into the set, then it is rejected if it contains repeated value of either attribute 'a' or 'b'.

Thanks in advance!

2 Answers

I'm not sure it's possible to compress an or condition like this into a single hash value. I tried experimenting with applying DeMorgan's law (not nand instead of or) but came up empty.

Your best bet for making the type hashable might just be to return a constant value (such that all instances have the same hash), and rely on the hashtable's collision behavior.

This is implicitly allowed by the standards, because the rule is

a == b implies hash(a) == hash(b)

and not

hash(a) == hash(b) implies a == b

which has never been the case (after all, hash collisions are expected to happen occasionally - a hash is only 32 or 64 bits large)


A set will accommodate for this behavior with its natural collision-avoidance behavior, and while this will not at all be performant, it will at least allow you to use the set data structure in the first place.

>>> class A:
...   def __init__(self, prop):
...     self.prop = prop
...   def __repr__(self):
...     return f'A({self.prop})'
...   def __eq__(self, other):
...     return self.prop == other.prop
...   def __hash__(self):
...     return 0
... 
>>> {A(1), A(2), A(3), A(1)}
{A(1), A(2), A(3)}

Admittedly, this kind of defeats the purpose of using a set, though there might be more point to it if you were using your objects as keys in a dict.

I think it's not possible and you shouldn't do that. Whatever you use in your __eq__ , should also be present in __hash__, otherwise:

let's say you only use hash of a in your __hash__, you would end up with a scenario that two equal objects have different hashes(because their bs are equal) which contradict the actual rule:

if obj1 == obj2 -> True then hash(obj1) == hash(obj2) "must" be True

Same with using only b in __hash__.

Related