(1) I have done a search in my codebase for __hash__, and I don’t find any. (As a check I found many occurences of just the string hash, but only as part of other words). I have also looked through the Class inheritance hierarchy for __hash__, and don’t find any.
(2) Nevertheless, when I do a diagnostic print of hash(my_child), it prints a value, and that value is different for different instances that have the same underlying data, so compare equal because they have the same ‘handle’. Perhaps that is the wrong call, and I should have used my_child.__hash__()?
(3) From what is said, I think that the Class is NOT broken, because:
If a class defines mutable objects and implements an __eq__()
method, it should not implement __hash__()
, since the implementation of hashable collections requires that a key’s hash value is immutable (if the object’s hash value changes, it will be in the wrong hash bucket).
My class is mutable, so defining __eq__, but not __hash__ is correct for the class.
(4) However, the documentation says:
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)
.
and this does not seem to be what happens (has this changed from older versions of Python, because this code was tested in Python 2.7.8)
Python 2.7 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)
.
(5) But I think this whole business of whether and how __hash__ is defined is a side issue. The documentation for Membership test operations clearly says:
For container types such as list, tuple, set, frozenset, dict, or collections.deque, the expression x in y
is equivalent to any(x is e or x == e for e in y)
.
(same in Python 2.7) And I think I am entitled to rely on this behaviour.
(6) In fact, for lists, the membership in a list does not seem to use hash as a preliminary shortcut, it just seems to use the == (__eq__) method.
TLDR: I think the documentation for 6.10.2 Membership test operations is wrong, it should say that the Python implementation is entitled to use __hash__ as a shortcut when determining “x in y”, (is that only for sets, and not for lists?)