Skip to content

Commit 61f1e67

Browse files
authored
GH-84783: Make the slice object hashable (GH-101264)
1 parent 5170caf commit 61f1e67

File tree

7 files changed

+53
-12
lines changed

7 files changed

+53
-12
lines changed

Doc/library/functions.rst

+3
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,9 @@ are always available. They are listed here in alphabetical order.
16351635
example: ``a[start:stop:step]`` or ``a[start:stop, i]``. See
16361636
:func:`itertools.islice` for an alternate version that returns an iterator.
16371637

1638+
.. versionchanged:: 3.12
1639+
Slice objects are now :term:`hashable` (provided :attr:`~slice.start`,
1640+
:attr:`~slice.stop`, and :attr:`~slice.step` are hashable).
16381641

16391642
.. function:: sorted(iterable, /, *, key=None, reverse=False)
16401643

Lib/test/test_capi/test_misc.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -419,11 +419,6 @@ def __setitem__(self, index, value):
419419
with self.assertRaises(TypeError):
420420
_testcapi.sequence_set_slice(None, 1, 3, 'xy')
421421

422-
mapping = {1: 'a', 2: 'b', 3: 'c'}
423-
with self.assertRaises(TypeError):
424-
_testcapi.sequence_set_slice(mapping, 1, 3, 'xy')
425-
self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'})
426-
427422
def test_sequence_del_slice(self):
428423
# Correct case:
429424
data = [1, 2, 3, 4, 5]
@@ -459,7 +454,7 @@ def __delitem__(self, index):
459454
_testcapi.sequence_del_slice(None, 1, 3)
460455

461456
mapping = {1: 'a', 2: 'b', 3: 'c'}
462-
with self.assertRaises(TypeError):
457+
with self.assertRaises(KeyError):
463458
_testcapi.sequence_del_slice(mapping, 1, 3)
464459
self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'})
465460

Lib/test/test_doctest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ def non_Python_modules(): r"""
707707
708708
>>> import builtins
709709
>>> tests = doctest.DocTestFinder().find(builtins)
710-
>>> 825 < len(tests) < 845 # approximate number of objects with docstrings
710+
>>> 830 < len(tests) < 850 # approximate number of objects with docstrings
711711
True
712712
>>> real_tests = [t for t in tests if len(t.examples) > 0]
713713
>>> len(real_tests) # objects that actually have doctests

Lib/test/test_slice.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,16 @@ def test_repr(self):
8080
self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)")
8181

8282
def test_hash(self):
83-
# Verify clearing of SF bug #800796
84-
self.assertRaises(TypeError, hash, slice(5))
83+
self.assertEqual(hash(slice(5)), slice(5).__hash__())
84+
self.assertEqual(hash(slice(1, 2)), slice(1, 2).__hash__())
85+
self.assertEqual(hash(slice(1, 2, 3)), slice(1, 2, 3).__hash__())
86+
self.assertNotEqual(slice(5), slice(6))
87+
88+
with self.assertRaises(TypeError):
89+
hash(slice(1, 2, []))
90+
8591
with self.assertRaises(TypeError):
86-
slice(5).__hash__()
92+
hash(slice(4, {}))
8793

8894
def test_cmp(self):
8995
s1 = slice(1, 2, 3)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make the slice object hashable.

Objects/sliceobject.c

+37-1
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,42 @@ slice_traverse(PySliceObject *v, visitproc visit, void *arg)
628628
return 0;
629629
}
630630

631+
/* code based on tuplehash() of Objects/tupleobject.c */
632+
#if SIZEOF_PY_UHASH_T > 4
633+
#define _PyHASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL)
634+
#define _PyHASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL)
635+
#define _PyHASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL)
636+
#define _PyHASH_XXROTATE(x) ((x << 31) | (x >> 33)) /* Rotate left 31 bits */
637+
#else
638+
#define _PyHASH_XXPRIME_1 ((Py_uhash_t)2654435761UL)
639+
#define _PyHASH_XXPRIME_2 ((Py_uhash_t)2246822519UL)
640+
#define _PyHASH_XXPRIME_5 ((Py_uhash_t)374761393UL)
641+
#define _PyHASH_XXROTATE(x) ((x << 13) | (x >> 19)) /* Rotate left 13 bits */
642+
#endif
643+
644+
static Py_hash_t
645+
slicehash(PySliceObject *v)
646+
{
647+
Py_uhash_t acc = _PyHASH_XXPRIME_5;
648+
#define _PyHASH_SLICE_PART(com) { \
649+
Py_uhash_t lane = PyObject_Hash(v->com); \
650+
if(lane == (Py_uhash_t)-1) { \
651+
return -1; \
652+
} \
653+
acc += lane * _PyHASH_XXPRIME_2; \
654+
acc = _PyHASH_XXROTATE(acc); \
655+
acc *= _PyHASH_XXPRIME_1; \
656+
}
657+
_PyHASH_SLICE_PART(start);
658+
_PyHASH_SLICE_PART(stop);
659+
_PyHASH_SLICE_PART(step);
660+
#undef _PyHASH_SLICE_PART
661+
if(acc == (Py_uhash_t)-1) {
662+
return 1546275796;
663+
}
664+
return acc;
665+
}
666+
631667
PyTypeObject PySlice_Type = {
632668
PyVarObject_HEAD_INIT(&PyType_Type, 0)
633669
"slice", /* Name of this type */
@@ -642,7 +678,7 @@ PyTypeObject PySlice_Type = {
642678
0, /* tp_as_number */
643679
0, /* tp_as_sequence */
644680
0, /* tp_as_mapping */
645-
PyObject_HashNotImplemented, /* tp_hash */
681+
(hashfunc)slicehash, /* tp_hash */
646682
0, /* tp_call */
647683
0, /* tp_str */
648684
PyObject_GenericGetAttr, /* tp_getattro */

Objects/tupleobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ tuplerepr(PyTupleObject *v)
288288

289289
/* Hash for tuples. This is a slightly simplified version of the xxHash
290290
non-cryptographic hash:
291-
- we do not use any parallellism, there is only 1 accumulator.
291+
- we do not use any parallelism, there is only 1 accumulator.
292292
- we drop the final mixing since this is just a permutation of the
293293
output space: it does not help against collisions.
294294
- at the end, we mangle the length with a single constant.

0 commit comments

Comments
 (0)