Thomas Herve added the comment:

I gave it a try. The following patch corrects 2 problems:
 * classes with only __lt__ and __gt__ defined are hashable
 * classes defining __eq__ get a meaningful exception.

I've restricted the hash_name_op to the bare minimum, but no tests
failed, so I supposed it was enough :).

Please review.

Added file: http://bugs.python.org/file8871/1549_1.diff

__________________________________
Tracker <[EMAIL PROTECTED]>
<http://bugs.python.org/issue1549>
__________________________________
Index: Objects/typeobject.c
===================================================================
--- Objects/typeobject.c        (revision 59318)
+++ Objects/typeobject.c        (working copy)
@@ -3230,14 +3230,8 @@
                type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
 }
 
-/* Map rich comparison operators to their __xx__ namesakes */
-static char *name_op[] = {
-    "__lt__",
-    "__le__",
+static char *hash_name_op[] = {
     "__eq__",
-    "__ne__",
-    "__gt__",
-    "__ge__",
     "__cmp__",
        /* These are only for overrides_hash(): */
     "__hash__",
@@ -3246,13 +3240,14 @@
 static int
 overrides_hash(PyTypeObject *type)
 {
-       int i;
+       char **p;
        PyObject *dict = type->tp_dict;
 
        assert(dict != NULL);
-       for (i = 0; i < 8; i++) {
-               if (PyDict_GetItemString(dict, name_op[i]) != NULL)
+       for (p = hash_name_op; *p; p++) {
+               if (PyDict_GetItemString(dict, *p) != NULL) {
                        return 1;
+        }
        }
        return 0;
 }
@@ -4846,7 +4841,7 @@
 
        func = lookup_method(self, "__hash__", &hash_str);
 
-       if (func != NULL) {
+       if (func != NULL && func != Py_None) {
                PyObject *res = PyEval_CallObject(func, NULL);
                Py_DECREF(func);
                if (res == NULL)
@@ -4971,6 +4966,15 @@
        return 0;
 }
 
+static char *name_op[] = {
+    "__lt__",
+    "__le__",
+    "__eq__",
+    "__ne__",
+    "__gt__",
+    "__ge__",
+};
+
 static PyObject *
 half_richcompare(PyObject *self, PyObject *other, int op)
 {
Index: Lib/test/test_richcmp.py
===================================================================
--- Lib/test/test_richcmp.py    (revision 59318)
+++ Lib/test/test_richcmp.py    (working copy)
@@ -85,6 +85,35 @@
             raise ValueError, "Cannot compare vectors of different length"
         return other
 
+
+class SimpleOrder(object):
+    """
+    A simple class that defines order but not full comparison.
+    """
+
+    def __init__(self, value):
+        self.value = value
+
+    def __lt__(self, other):
+        if not isinstance(other, SimpleOrder):
+            return True
+        return self.value < other.value
+
+    def __gt__(self, other):
+        if not isinstance(other, SimpleOrder):
+            return False
+        return self.value > other.value
+
+
+class DumbEqualityWithoutHash(object):
+    """
+    A class that define __eq__, but no __hash__: it shouldn't be hashable.
+    """
+
+    def __eq__(self, other):
+        return False
+
+
 opmap = {
     "lt": (lambda a,b: a< b, operator.lt, operator.__lt__),
     "le": (lambda a,b: a<=b, operator.le, operator.__le__),
@@ -330,8 +359,39 @@
         for op in opmap["lt"]:
             self.assertIs(op(x, y), True)
 
+
+class HashableTest(unittest.TestCase):
+    """
+    Test hashability of classes with rich operators defined.
+    """
+
+    def test_simpleOrderHashable(self):
+        """
+        A class that only defines __gt__ and/or __lt__ should be hashable.
+        """
+        a = SimpleOrder(1)
+        b = SimpleOrder(2)
+        self.assert_(a < b)
+        self.assert_(b > a)
+        self.assert_(a.__hash__ is not None)
+
+    def test_notHashableException(self):
+        """
+        If a class is not hashable, it should raise a TypeError with an
+        understandable message.
+        """
+        a = DumbEqualityWithoutHash()
+        try:
+            hash(a)
+        except TypeError, e:
+            self.assertEquals(str(e),
+                              "unhashable type: 'DumbEqualityWithoutHash'")
+        else:
+            raise test_support.TestFailed("Should not be here")
+
+
 def test_main():
-    test_support.run_unittest(VectorTest, NumberTest, MiscTest, DictTest, 
ListTest)
+    test_support.run_unittest(VectorTest, NumberTest, MiscTest, DictTest, 
ListTest, HashableTest)
 
 if __name__ == "__main__":
     test_main()
_______________________________________________
Python-bugs-list mailing list 
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to