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.

3
  • 1
    Possible duplicate: stackoverflow.com/questions/55753498/… Commented May 24, 2019 at 19:54
  • In any case, because if you define an __eq__, you must define a compatible __hash__. If you don't define an __eq__, user-defined types hash based on identity by default Commented May 24, 2019 at 19:55
  • @juanpa.arrivillaga Ok, in logic it make sense. And it actually work. Commented May 24, 2019 at 20:12

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

4 Comments

Ok, maybe I can create my own hash method, but this still does not explain why and how __eq__ can impact the hashability.
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.
Hashable absolutely does mean 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.
More precisely, __hash__ should only be defined for objects where everything involved in == comparisons is immutable. Functions and generators, for example, are both mutable and hashable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.