2

I found an interesting observation lately, that there are things to impact the hashability of an object/instance of a class. And I am wondering how and why?

For example, I got a linked list class called ListNode:

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

   def __repr__(self):
        if self.next:
            return "{}->{}".format(self.val, repr(self.next))
        else:
            return "{}".format(self.val)

    # def __eq__(self, other):
        # if not self and not other:
        #     return True
        # elif not self or not other:
        #     return False
        # else:
        #     return self.val == other.val and self.next == other.next

     # def __eq__(self, other):
        # return str(self) == str(other)

Notice that I blocked the __eq__ method. Now if I create an instance:

A = ListNode(1)
B = ListNode(2)
C = ListNode(3)
A.next = B
B.next = C
print(hash(A))

Then it is hashable, however, I do get different output number everytime I run it.

Now if I unblock the __eq__ method, all of sudden it is not hashable anymore. Why?

It seems that the hash method will use __eq__. And how does it know it is not hashable after __eq__ is enabled?

Additional: If I write the __eq__ method to just compare the str version of the two linked list (the second __eq__ method), I thought this could solve the problem, because by converting the linked list into a string, it becomes an hashable data, but I still get the unhashable error message

Thanks!

According to @juanpa.arrivillaga's comment:

__eq__ will remove the default __hash__ method, making it unhashable. So I added my own__hash__ mehtod:

def __hash__(self):
    return hash(id(self))

This solved the problem and made ListNode hashable again with __eq__ enabled.

jxie0755
  • 1,682
  • 1
  • 16
  • 35

1 Answers1

3

If a class does not define an __eq__() method it should not define a __hash__() operation either; if it defines __eq__() but not __hash__(), its instances will not be usable as items in hashable collections.

(...)

A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable when checking isinstance(obj, collections.abc.Hashable). 1

The introduction of the __eq__() method hence sets the __hash__() to None. You could add a custom hash, to allow for the construction above:

def __hash__(self):
    return self.val

More information you can find here: https://docs.python.org/3/reference/datamodel.html#object.hash

Simon
  • 5,464
  • 6
  • 49
  • 85
  • 1
    Ok, maybe I can create my own hash method, but this still does not explain why and how `__eq__` can impact the hashability. – jxie0755 May 24 '19 at 19:57
  • 2
    Hashable does not mean immutable. You can define a valid hash function for a mutable object as long as you don't take any of the mutable parts into consideration. – chepner May 24 '19 at 19:57
  • 3
    **[Hashable absolutely *does* mean immutable](https://inventwithpython.com/blog/2019/02/01/hashable-objects-must-be-immutable).** Yes, third-party developers technically *can* violate this semantic constraint – but doing so renders the resulting types non-deterministically insane and unusable in most common hashable contexts (e.g., `dict` keys, `set` items), which is bad. Ergo, nobody in the history of Python would ever do or has ever done this. Ergo, hashable == immutable. – Cecil Curry Jul 02 '20 at 06:38