From 389381564cfa936c8e50bc51dc53b55cba857652 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 21 Aug 2006 23:36:26 +0000 Subject: 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! --- BROKEN | 61 +---------------------------------------------- Include/object.h | 1 + Lib/test/mapping_tests.py | 4 ++++ Lib/test/test_dict.py | 4 ++++ Objects/object.c | 9 ++++++- Objects/typeobject.c | 58 +++++++++++++++++++++++++++++++++++--------- 6 files changed, 65 insertions(+), 72 deletions(-) diff --git a/BROKEN b/BROKEN index 53e9d43..4747794 100644 --- a/BROKEN +++ b/BROKEN @@ -1,60 +1 @@ -//////////////////////////////////////////////////////////////////////// -test_class -//////////////////////////////////////////////////////////////////////// - -test test_class failed -- hash(C1()) should raise -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 - 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 +(Nothing is broken at the moment AFAIK.) diff --git a/Include/object.h b/Include/object.h index 1f1aeaa..ab42f8a 100644 --- a/Include/object.h +++ b/Include/object.h @@ -368,6 +368,7 @@ PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); /* Generic operations on objects */ PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); +PyAPI_FUNC(void) _Py_Break(void); PyAPI_FUNC(void) _PyObject_Dump(PyObject *); PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *); PyAPI_FUNC(PyObject *) _PyObject_Str(PyObject *); diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py index 1c570b6..b917540 100644 --- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -545,6 +545,8 @@ class TestHashMappingProtocol(TestMappingProtocol): class BadEq(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 24 d = self._empty_mapping() d[BadEq()] = 42 @@ -630,6 +632,8 @@ class TestHashMappingProtocol(TestMappingProtocol): class BadCmp(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 42 d1 = self._full_mapping({BadCmp(): 1}) d2 = self._full_mapping({1: 1}) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index f168846..7295f41 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -76,6 +76,8 @@ class DictTest(unittest.TestCase): class BadEq(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 24 d = {} d[BadEq()] = 42 @@ -375,6 +377,8 @@ class DictTest(unittest.TestCase): class BadCmp(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 42 d1 = {BadCmp(): 1} d2 = {1: 1} diff --git a/Objects/object.c b/Objects/object.c index cb60320..80111b4 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -320,9 +320,16 @@ PyObject_Print(PyObject *op, FILE *fp, int flags) 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 */ -void _PyObject_Dump(PyObject* op) +void +_PyObject_Dump(PyObject* op) { if (op == NULL) fprintf(stderr, "NULL\n"); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1578801..f30a826 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2847,6 +2847,33 @@ inherit_special(PyTypeObject *type, PyTypeObject *base) 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 inherit_slots(PyTypeObject *type, PyTypeObject *base) { @@ -2970,9 +2997,12 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) COPYSLOT(tp_call); COPYSLOT(tp_str); { + /* Copy comparison-related slots only when + not overriding them anywhere */ if (type->tp_compare == 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_richcompare = base->tp_richcompare; @@ -3020,6 +3050,10 @@ PyType_Ready(PyTypeObject *type) PyTypeObject *base; Py_ssize_t i, n; + if (strcmp(type->tp_name, "C") == 0) { + _Py_Break(); + } + if (type->tp_flags & Py_TPFLAGS_READY) { assert(type->tp_dict != NULL); 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 */ base = type->tp_base; if (base != NULL) { @@ -4450,16 +4496,6 @@ slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value) return 0; } -/* Map rich comparison operators to their __xx__ namesakes */ -static char *name_op[] = { - "__lt__", - "__le__", - "__eq__", - "__ne__", - "__gt__", - "__ge__", -}; - static PyObject * half_richcompare(PyObject *self, PyObject *other, int op) { -- cgit v0.12