Change the way __hash__ is inherited; when __eq__ or __cmp__ is overridden

but __hash__ is not, set __hash__ explicitly to None (and tp_hash to NULL).
All unit tests pass now!
This commit is contained in:
Guido van Rossum 2006-08-21 23:36:26 +00:00
parent 5431ee4a49
commit 389381564c
6 changed files with 65 additions and 72 deletions

61
BROKEN
View File

@ -1,60 +1 @@
//////////////////////////////////////////////////////////////////////// (Nothing is broken at the moment AFAIK.)
test_class
////////////////////////////////////////////////////////////////////////
test test_class failed -- hash(C1()) should raise <class 'exceptions.TypeError'>
Also hash(C2())
Also stack blowout, recursing between
#5921 0x0003868c in slot_tp_call (self=0x5b0c90, args=0x338030, kwds=0x0) at ../Objects/typeobject.c:4583
#5922 0x00021124 in PyObject_Call (func=0x5b0c90, arg=0x3384c0, kw=0x134e10) at ../Objects/abstract.c:1791
////////////////////////////////////////////////////////////////////////
test_descr
////////////////////////////////////////////////////////////////////////
Testing hash of mutable subclasses...
Traceback (most recent call last):
File "../Lib/test/test_descr.py", line 4096, in <module>
test_main()
File "../Lib/test/test_descr.py", line 4059, in test_main
hashinherit()
File "../Lib/test/test_descr.py", line 3108, in hashinherit
raise TestFailed, "hash() of dict subclass should fail"
test.test_support.TestFailed: hash() of dict subclass should fail
////////////////////////////////////////////////////////////////////////
test_set
////////////////////////////////////////////////////////////////////////
======================================================================
FAIL: test_contains (__main__.TestSetSubclass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "../Lib/test/test_set.py", line 52, in test_contains
self.assert_(self.thetype(self.letters) in s)
AssertionError
======================================================================
FAIL: test_discard (__main__.TestSetSubclass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "../Lib/test/test_set.py", line 302, in test_discard
self.assert_(self.thetype(self.word) in s)
AssertionError
======================================================================
FAIL: test_hash (__main__.TestSetSubclass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "../Lib/test/test_set.py", line 265, in test_hash
self.assertRaises(TypeError, hash, self.s)
AssertionError: TypeError not raised
======================================================================
FAIL: test_remove (__main__.TestSetSubclass)
----------------------------------------------------------------------
Traceback (most recent call last):
File "../Lib/test/test_set.py", line 291, in test_remove
self.assert_(self.thetype(self.word) in s)
AssertionError

View File

@ -368,6 +368,7 @@ PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);
/* Generic operations on objects */ /* Generic operations on objects */
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
PyAPI_FUNC(void) _Py_Break(void);
PyAPI_FUNC(void) _PyObject_Dump(PyObject *); PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *); PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *);
PyAPI_FUNC(PyObject *) _PyObject_Str(PyObject *); PyAPI_FUNC(PyObject *) _PyObject_Str(PyObject *);

View File

@ -545,6 +545,8 @@ class TestHashMappingProtocol(TestMappingProtocol):
class BadEq(object): class BadEq(object):
def __eq__(self, other): def __eq__(self, other):
raise Exc() raise Exc()
def __hash__(self):
return 24
d = self._empty_mapping() d = self._empty_mapping()
d[BadEq()] = 42 d[BadEq()] = 42
@ -630,6 +632,8 @@ class TestHashMappingProtocol(TestMappingProtocol):
class BadCmp(object): class BadCmp(object):
def __eq__(self, other): def __eq__(self, other):
raise Exc() raise Exc()
def __hash__(self):
return 42
d1 = self._full_mapping({BadCmp(): 1}) d1 = self._full_mapping({BadCmp(): 1})
d2 = self._full_mapping({1: 1}) d2 = self._full_mapping({1: 1})

View File

@ -76,6 +76,8 @@ class DictTest(unittest.TestCase):
class BadEq(object): class BadEq(object):
def __eq__(self, other): def __eq__(self, other):
raise Exc() raise Exc()
def __hash__(self):
return 24
d = {} d = {}
d[BadEq()] = 42 d[BadEq()] = 42
@ -375,6 +377,8 @@ class DictTest(unittest.TestCase):
class BadCmp(object): class BadCmp(object):
def __eq__(self, other): def __eq__(self, other):
raise Exc() raise Exc()
def __hash__(self):
return 42
d1 = {BadCmp(): 1} d1 = {BadCmp(): 1}
d2 = {1: 1} d2 = {1: 1}

View File

@ -320,9 +320,16 @@ PyObject_Print(PyObject *op, FILE *fp, int flags)
return internal_print(op, fp, flags, 0); return internal_print(op, fp, flags, 0);
} }
/* For debugging convenience. Set a breakpoint here and call it from your DLL */
void
_Py_Break(void)
{
}
/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */ /* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
void _PyObject_Dump(PyObject* op) void
_PyObject_Dump(PyObject* op)
{ {
if (op == NULL) if (op == NULL)
fprintf(stderr, "NULL\n"); fprintf(stderr, "NULL\n");

View File

@ -2847,6 +2847,33 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
COPYVAL(tp_dictoffset); COPYVAL(tp_dictoffset);
} }
/* Map rich comparison operators to their __xx__ namesakes */
static char *name_op[] = {
"__lt__",
"__le__",
"__eq__",
"__ne__",
"__gt__",
"__ge__",
/* These are only for overrides_cmp_or_hash(): */
"__cmp__",
"__hash__",
};
static int
overrides_cmp_or_hash(PyTypeObject *type)
{
int i;
PyObject *dict = type->tp_dict;
assert(dict != NULL);
for (i = 0; i < 8; i++) {
if (PyDict_GetItemString(dict, name_op[i]) != NULL)
return 1;
}
return 0;
}
static void static void
inherit_slots(PyTypeObject *type, PyTypeObject *base) inherit_slots(PyTypeObject *type, PyTypeObject *base)
{ {
@ -2970,9 +2997,12 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
COPYSLOT(tp_call); COPYSLOT(tp_call);
COPYSLOT(tp_str); COPYSLOT(tp_str);
{ {
/* Copy comparison-related slots only when
not overriding them anywhere */
if (type->tp_compare == NULL && if (type->tp_compare == NULL &&
type->tp_richcompare == NULL && type->tp_richcompare == NULL &&
type->tp_hash == NULL) type->tp_hash == NULL &&
!overrides_cmp_or_hash(type))
{ {
type->tp_compare = base->tp_compare; type->tp_compare = base->tp_compare;
type->tp_richcompare = base->tp_richcompare; type->tp_richcompare = base->tp_richcompare;
@ -3020,6 +3050,10 @@ PyType_Ready(PyTypeObject *type)
PyTypeObject *base; PyTypeObject *base;
Py_ssize_t i, n; Py_ssize_t i, n;
if (strcmp(type->tp_name, "C") == 0) {
_Py_Break();
}
if (type->tp_flags & Py_TPFLAGS_READY) { if (type->tp_flags & Py_TPFLAGS_READY) {
assert(type->tp_dict != NULL); assert(type->tp_dict != NULL);
return 0; return 0;
@ -3150,6 +3184,18 @@ PyType_Ready(PyTypeObject *type)
} }
} }
/* Hack for tp_hash and __hash__.
If after all that, tp_hash is still NULL, and __hash__ is not in
tp_dict, set tp_dict['__hash__'] equal to None.
This signals that __hash__ is not inherited.
*/
if (type->tp_hash == NULL) {
if (PyDict_GetItemString(type->tp_dict, "__hash__") == NULL) {
if (PyDict_SetItemString(type->tp_dict, "__hash__", Py_None) < 0)
goto error;
}
}
/* Some more special stuff */ /* Some more special stuff */
base = type->tp_base; base = type->tp_base;
if (base != NULL) { if (base != NULL) {
@ -4450,16 +4496,6 @@ slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value)
return 0; return 0;
} }
/* Map rich comparison operators to their __xx__ namesakes */
static char *name_op[] = {
"__lt__",
"__le__",
"__eq__",
"__ne__",
"__gt__",
"__ge__",
};
static PyObject * static PyObject *
half_richcompare(PyObject *self, PyObject *other, int op) half_richcompare(PyObject *self, PyObject *other, int op)
{ {