Fix of SF bug #475877 (Mutable subtype instances are hashable).

Rather than tweaking the inheritance of type object slots (which turns
out to be too messy to try), this fix adds a __hash__ to the list and
dict types (the only mutable types I'm aware of) that explicitly
raises an error.  This has the advantage that list.__hash__([]) also
raises an error (previously, this would invoke object.__hash__([]),
returning the argument's address); ditto for dict.__hash__.

The disadvantage for this fix is that 3rd party mutable types aren't
automatically fixed.  This should be added to the rules for creating
subclassable extension types: if you don't want your object to be
hashable, add a tp_hash function that raises an exception.

Also, it's possible that I've forgotten about other mutable types for
which this should be done.
This commit is contained in:
Guido van Rossum 2001-12-03 16:32:18 +00:00
parent cb33165ca2
commit dbb53d9918
3 changed files with 41 additions and 3 deletions

View File

@ -2571,6 +2571,29 @@ def delhook():
del c del c
vereq(log, [1]) vereq(log, [1])
def hashinherit():
if verbose: print "Testing hash of mutable subclasses..."
class mydict(dict):
pass
d = mydict()
try:
hash(d)
except TypeError:
pass
else:
raise TestFailed, "hash() of dict subclass should fail"
class mylist(list):
pass
d = mylist()
try:
hash(d)
except TypeError:
pass
else:
raise TestFailed, "hash() of list subclass should fail"
def test_main(): def test_main():
class_docstrings() class_docstrings()
lists() lists()
@ -2623,6 +2646,7 @@ def test_main():
str_of_str_subclass() str_of_str_subclass()
kwdargs() kwdargs()
delhook() delhook()
hashinherit()
if verbose: print "All OK" if verbose: print "All OK"
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1797,6 +1797,13 @@ dict_init(PyObject *self, PyObject *args, PyObject *kwds)
return result; return result;
} }
static long
dict_nohash(PyObject *self)
{
PyErr_SetString(PyExc_TypeError, "dict objects are unhashable");
return -1;
}
static PyObject * static PyObject *
dict_iter(dictobject *dict) dict_iter(dictobject *dict)
{ {
@ -1827,7 +1834,7 @@ PyTypeObject PyDict_Type = {
0, /* tp_as_number */ 0, /* tp_as_number */
&dict_as_sequence, /* tp_as_sequence */ &dict_as_sequence, /* tp_as_sequence */
&dict_as_mapping, /* tp_as_mapping */ &dict_as_mapping, /* tp_as_mapping */
0, /* tp_hash */ dict_nohash, /* tp_hash */
0, /* tp_call */ 0, /* tp_call */
0, /* tp_str */ 0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericGetAttr, /* tp_getattro */

View File

@ -1617,6 +1617,13 @@ list_init(PyListObject *self, PyObject *args, PyObject *kw)
return 0; return 0;
} }
static long
list_nohash(PyObject *self)
{
PyErr_SetString(PyExc_TypeError, "list objects are unhashable");
return -1;
}
static char append_doc[] = static char append_doc[] =
"L.append(object) -- append object to end"; "L.append(object) -- append object to end";
static char extend_doc[] = static char extend_doc[] =
@ -1681,7 +1688,7 @@ PyTypeObject PyList_Type = {
0, /* tp_as_number */ 0, /* tp_as_number */
&list_as_sequence, /* tp_as_sequence */ &list_as_sequence, /* tp_as_sequence */
0, /* tp_as_mapping */ 0, /* tp_as_mapping */
0, /* tp_hash */ list_nohash, /* tp_hash */
0, /* tp_call */ 0, /* tp_call */
0, /* tp_str */ 0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericGetAttr, /* tp_getattro */
@ -1771,7 +1778,7 @@ static PyTypeObject immutable_list_type = {
0, /* tp_as_number */ 0, /* tp_as_number */
&immutable_list_as_sequence, /* tp_as_sequence */ &immutable_list_as_sequence, /* tp_as_sequence */
0, /* tp_as_mapping */ 0, /* tp_as_mapping */
0, /* tp_hash */ list_nohash, /* tp_hash */
0, /* tp_call */ 0, /* tp_call */
0, /* tp_str */ 0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericGetAttr, /* tp_getattro */