This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: New-style classes with __eq__ but not __hash__ are hashable
Type: Stage:
Components: Interpreter Core Versions: Python 2.3
process
Status: closed Resolution: duplicate
Dependencies: Superseder:
Assigned To: Nosy List: edloper, rhettinger
Priority: normal Keywords:

Created on 2003-11-30 05:40 by edloper, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Messages (4)
msg19206 - (view) Author: Edward Loper (edloper) * (Python triager) Date: 2003-11-30 05:40
According to the current reference docs, "If [a class] 
defines
__cmp__() or __eq__() but not __hash__(), its 
instances will not be
usable as dictionary keys. [1]  But this doesn't work 
quite like you'd
think for new-style classes:

    Python 2.3 (#1, Sep 13 2003, 00:49:11) 
    [GCC 3.3 20030304 (Apple Computer, Inc. build 
1495)] on darwin
    Type "help", "copyright", "credits" or "license" for 
more information.
    >>> class A(object):
    ...     def __cmp__(self, other): return -1
    >>> print {A():1}
    {<__main__.A object at 0x71cf0>: 1}

The problem is that object defines a default __hash__ 
method:

    >>> print A.__hash__
    <slot wrapper '__hash__' of 'object' objects>

So the dictionary class thinks that the object is 
hashable.  But given
that we've overridden cmp, there's no reason to believe 
that __hash__
is still valid.  The only workaround I've found is to 
manually add a
__hash__ method that raises the appropriate 
exception:

    >>> class A(object):
    ...     def __cmp__(self, other): return -1
    ...     def __hash__(self): 
    ...         raise TypeError, ('%s objects are unhashable' 
% 
    ...                           self.__class__)

But it seems like this should be fixed in Python itself.  I 
can think
of 2 reasonable ways to fix it:

  - change object.__hash__() to raise a TypeError if 
__cmp__ or
    __eq__ is overridden.
  - change hash() to raise a TypeError if given an object 
that
    overrides __cmp__ or __eq__ but not __hash__.

So..  Is this a real bug, or am I missing something?  And 
if so,
what's the prefered place to fix it?  (I'd be happy to try 
to put
together a patch for it, if it is indeed broken.)

-Edward

[1] http://www.python.org/doc/current/ref/
customization.html
msg19207 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2003-12-01 10:52
Logged In: YES 
user_id=80475

It has been a subject of debate but the behavior is already
cast in stone.  Anything inheriting from object is hashable
by default.

The preferred way to make things unhashable is:

def __hash__(self)
    return NotImplemented
msg19208 - (view) Author: Edward Loper (edloper) * (Python triager) Date: 2003-12-01 14:49
Logged In: YES 
user_id=195958

Can you point me to the debate?  I searched the python & 
python-dev mailing lists, and only came up with 
statements that suggested that people think that it does 
have the documented behavior.  E.g., "A new-style class 
would NOT become unhashable by implementing __eq__
w/o __hash__, although its INSTANCES would." 
<http://groups.yahoo.com/group/python-
list/message/108397>

Using "return NotImplemented" does *not* seem like the 
right thing to do: if I try to use such an object as a 
dictionary key, it gives the confusing error "TypeError: an 
integer is required," since dict expects hash() to return an 
int.

If this behavior is indeed set in stone, then this should be 
changed to a documentation bug, and the originally 
referenced page 
<://www.python.org/doc/current/ref/customization.html> 
should be updated to describe the actual behavior for new-
style classes.

-Edward
msg19209 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2003-12-02 07:17
Logged In: YES 
user_id=80475

I should have been clearer.

The bug has been discussed several times before (SF 475877,
660098, and 730087) and while  unresolved leaves us in a
workable position of explicitly defining a nohash function.  

I rechecked my notes, the right way to implement such a
function is to raise a TypeError.  I misrememberes returning
NotImplemented which is the technique for overcoming certain
issues related to __cmp__.

History
Date User Action Args
2022-04-11 14:56:01adminsetgithub: 39631
2003-11-30 05:40:01edlopercreate