]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-84783: Make the slice object hashable (GH-101264)
authorFurkan Onder <furkanonder@protonmail.com>
Sun, 19 Feb 2023 00:22:02 +0000 (00:22 +0000)
committerGitHub <noreply@github.com>
Sun, 19 Feb 2023 00:22:02 +0000 (18:22 -0600)
Doc/library/functions.rst
Lib/test/test_capi/test_misc.py
Lib/test/test_doctest.py
Lib/test/test_slice.py
Misc/NEWS.d/next/Core and Builtins/2023-02-11-23-14-06.gh-issue-84783._P5sMa.rst [new file with mode: 0644]
Objects/sliceobject.c
Objects/tupleobject.c

index 658d6768457d16a2fe4d30a71fccf90905ad0419..3ff2884902515331911ef33727e81b994137f74d 100644 (file)
@@ -1635,6 +1635,9 @@ are always available.  They are listed here in alphabetical order.
    example: ``a[start:stop:step]`` or ``a[start:stop, i]``.  See
    :func:`itertools.islice` for an alternate version that returns an iterator.
 
+   .. versionchanged:: 3.12
+      Slice objects are now :term:`hashable` (provided :attr:`~slice.start`,
+      :attr:`~slice.stop`, and :attr:`~slice.step` are hashable).
 
 .. function:: sorted(iterable, /, *, key=None, reverse=False)
 
index f26b4723d1e68b838b58f7a6f05b8574e118297d..ad099c61463b664a614dc553c9809048dc0544a5 100644 (file)
@@ -419,11 +419,6 @@ class CAPITest(unittest.TestCase):
         with self.assertRaises(TypeError):
             _testcapi.sequence_set_slice(None, 1, 3, 'xy')
 
-        mapping = {1: 'a', 2: 'b', 3: 'c'}
-        with self.assertRaises(TypeError):
-            _testcapi.sequence_set_slice(mapping, 1, 3, 'xy')
-        self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'})
-
     def test_sequence_del_slice(self):
         # Correct case:
         data = [1, 2, 3, 4, 5]
@@ -459,7 +454,7 @@ class CAPITest(unittest.TestCase):
             _testcapi.sequence_del_slice(None, 1, 3)
 
         mapping = {1: 'a', 2: 'b', 3: 'c'}
-        with self.assertRaises(TypeError):
+        with self.assertRaises(KeyError):
             _testcapi.sequence_del_slice(mapping, 1, 3)
         self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'})
 
index 65e215f1cdda4a6f8e5f098fd7e3019a04d0230a..3491d4cdb1c18bdd304872029cff701fa9d29ccc 100644 (file)
@@ -707,7 +707,7 @@ plain ol' Python and is guaranteed to be available.
 
     >>> import builtins
     >>> tests = doctest.DocTestFinder().find(builtins)
-    >>> 825 < len(tests) < 845 # approximate number of objects with docstrings
+    >>> 830 < len(tests) < 850 # approximate number of objects with docstrings
     True
     >>> real_tests = [t for t in tests if len(t.examples) > 0]
     >>> len(real_tests) # objects that actually have doctests
index 03fde3275e1475dc6467cf78270d9102c55da9bd..c35a2293f790a2b40a6f7767a72fa19614d2144b 100644 (file)
@@ -80,10 +80,16 @@ class SliceTest(unittest.TestCase):
         self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)")
 
     def test_hash(self):
-        # Verify clearing of SF bug #800796
-        self.assertRaises(TypeError, hash, slice(5))
+        self.assertEqual(hash(slice(5)), slice(5).__hash__())
+        self.assertEqual(hash(slice(1, 2)), slice(1, 2).__hash__())
+        self.assertEqual(hash(slice(1, 2, 3)), slice(1, 2, 3).__hash__())
+        self.assertNotEqual(slice(5), slice(6))
+
+        with self.assertRaises(TypeError):
+            hash(slice(1, 2, []))
+
         with self.assertRaises(TypeError):
-            slice(5).__hash__()
+            hash(slice(4, {}))
 
     def test_cmp(self):
         s1 = slice(1, 2, 3)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-02-11-23-14-06.gh-issue-84783._P5sMa.rst b/Misc/NEWS.d/next/Core and Builtins/2023-02-11-23-14-06.gh-issue-84783._P5sMa.rst
new file mode 100644 (file)
index 0000000..e1c851a
--- /dev/null
@@ -0,0 +1 @@
+Make the slice object hashable.
index 5694bd9c661fa531c903e14687fddf5215318b08..5d2e6ad522bcf26dbc6c377caee0232dc8c68940 100644 (file)
@@ -628,6 +628,42 @@ slice_traverse(PySliceObject *v, visitproc visit, void *arg)
     return 0;
 }
 
+/* code based on tuplehash() of Objects/tupleobject.c */
+#if SIZEOF_PY_UHASH_T > 4
+#define _PyHASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL)
+#define _PyHASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL)
+#define _PyHASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL)
+#define _PyHASH_XXROTATE(x) ((x << 31) | (x >> 33))  /* Rotate left 31 bits */
+#else
+#define _PyHASH_XXPRIME_1 ((Py_uhash_t)2654435761UL)
+#define _PyHASH_XXPRIME_2 ((Py_uhash_t)2246822519UL)
+#define _PyHASH_XXPRIME_5 ((Py_uhash_t)374761393UL)
+#define _PyHASH_XXROTATE(x) ((x << 13) | (x >> 19))  /* Rotate left 13 bits */
+#endif
+
+static Py_hash_t
+slicehash(PySliceObject *v)
+{
+    Py_uhash_t acc = _PyHASH_XXPRIME_5;
+#define _PyHASH_SLICE_PART(com) { \
+    Py_uhash_t lane = PyObject_Hash(v->com); \
+    if(lane == (Py_uhash_t)-1) { \
+        return -1; \
+    } \
+    acc += lane * _PyHASH_XXPRIME_2; \
+    acc = _PyHASH_XXROTATE(acc); \
+    acc *= _PyHASH_XXPRIME_1; \
+}
+    _PyHASH_SLICE_PART(start);
+    _PyHASH_SLICE_PART(stop);
+    _PyHASH_SLICE_PART(step);
+#undef _PyHASH_SLICE_PART
+    if(acc == (Py_uhash_t)-1) {
+        return 1546275796;
+    }
+    return acc;
+}
+
 PyTypeObject PySlice_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     "slice",                    /* Name of this type */
@@ -642,7 +678,7 @@ PyTypeObject PySlice_Type = {
     0,                                          /* tp_as_number */
     0,                                          /* tp_as_sequence */
     0,                                          /* tp_as_mapping */
-    PyObject_HashNotImplemented,                /* tp_hash */
+    (hashfunc)slicehash,                        /* tp_hash */
     0,                                          /* tp_call */
     0,                                          /* tp_str */
     PyObject_GenericGetAttr,                    /* tp_getattro */
index e1b9953226c0d749e3f27ac55b840ebbefcbf776..7d6d0e1bea249e89d2e5c6fb1f7ffe4605c0f850 100644 (file)
@@ -288,7 +288,7 @@ error:
 
 /* Hash for tuples. This is a slightly simplified version of the xxHash
    non-cryptographic hash:
-   - we do not use any parallellism, there is only 1 accumulator.
+   - we do not use any parallelism, there is only 1 accumulator.
    - we drop the final mixing since this is just a permutation of the
      output space: it does not help against collisions.
    - at the end, we mangle the length with a single constant.