1

I've come across an example of weird behavior when transitioning some code from python 2 to python 3. Below's a minimal (?) example of it:


class Bar(object):
    def __init__(self, x):
        self.x = x
    def __eq__(self, other):
        return self.x == other.x

b = Bar(1)
print(hash(b))

when run with python2, this code produces some output (a hash of Bar(1)), while python3 causes a TypeError: unhashable type: 'Bar'

this means that __hash__ is somehow inherited (from object ?) in python 2.

So, my questions are: what is the hash of Bar(1) in python 2? And why is the behaviour different?

Jytug
  • 1,072
  • 2
  • 11
  • 29

2 Answers2

4

Yes, the data model has changed. In Python 3:

User-defined classes have __eq__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns an appropriate value such that x == y implies both that x is y and hash(x) == hash(y).

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).

So, since you've defined a __eq__ explicitely, but did not define a __hash__, Python 3 objects will implicitely have __hash__ = None, causing the objects to be unhashable

In Python 2:

User-defined classes have __cmp__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns a result derived from id(x).

So it is hashing based on identity, which is a problem, because it isn't consistent with your __eq__. This is a reason that Python 3 switched behaviors.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
2

From https://docs.python.org/3/reference/datamodel.html#object.hash

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).

See https://docs.python.org/2/reference/datamodel.html#object.hash for the Python 2 version.

Tané Tachyon
  • 1,092
  • 8
  • 11
  • The second link only says "x.__hash__() returns a result derived from id(x).". How do I see what how the result is derived? – Jytug Apr 18 '19 at 21:10
  • You can see some discussions of that regarding different versions of Python in the comments here: https://stackoverflow.com/questions/11324271/what-is-the-default-hash-in-python/11324771 – Tané Tachyon Apr 18 '19 at 21:46