GH-84783: Make the slice object hashable (GH-101264)

This commit is contained in:
Furkan Onder 2023-02-19 00:22:02 +00:00 committed by GitHub
parent 5170caf305
commit 61f1e67c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 53 additions and 12 deletions

View 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 example: ``a[start:stop:step]`` or ``a[start:stop, i]``. See
:func:`itertools.islice` for an alternate version that returns an iterator. :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) .. function:: sorted(iterable, /, *, key=None, reverse=False)

View File

@ -419,11 +419,6 @@ class CAPITest(unittest.TestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
_testcapi.sequence_set_slice(None, 1, 3, 'xy') _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): def test_sequence_del_slice(self):
# Correct case: # Correct case:
data = [1, 2, 3, 4, 5] data = [1, 2, 3, 4, 5]
@ -459,7 +454,7 @@ class CAPITest(unittest.TestCase):
_testcapi.sequence_del_slice(None, 1, 3) _testcapi.sequence_del_slice(None, 1, 3)
mapping = {1: 'a', 2: 'b', 3: 'c'} mapping = {1: 'a', 2: 'b', 3: 'c'}
with self.assertRaises(TypeError): with self.assertRaises(KeyError):
_testcapi.sequence_del_slice(mapping, 1, 3) _testcapi.sequence_del_slice(mapping, 1, 3)
self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'}) self.assertEqual(mapping, {1: 'a', 2: 'b', 3: 'c'})

View File

@ -707,7 +707,7 @@ plain ol' Python and is guaranteed to be available.
>>> import builtins >>> import builtins
>>> tests = doctest.DocTestFinder().find(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 True
>>> real_tests = [t for t in tests if len(t.examples) > 0] >>> real_tests = [t for t in tests if len(t.examples) > 0]
>>> len(real_tests) # objects that actually have doctests >>> len(real_tests) # objects that actually have doctests

View File

@ -80,10 +80,16 @@ class SliceTest(unittest.TestCase):
self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)") self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)")
def test_hash(self): def test_hash(self):
# Verify clearing of SF bug #800796 self.assertEqual(hash(slice(5)), slice(5).__hash__())
self.assertRaises(TypeError, hash, slice(5)) 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): with self.assertRaises(TypeError):
slice(5).__hash__() hash(slice(1, 2, []))
with self.assertRaises(TypeError):
hash(slice(4, {}))
def test_cmp(self): def test_cmp(self):
s1 = slice(1, 2, 3) s1 = slice(1, 2, 3)

View File

@ -0,0 +1 @@
Make the slice object hashable.

View File

@ -628,6 +628,42 @@ slice_traverse(PySliceObject *v, visitproc visit, void *arg)
return 0; 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 = { PyTypeObject PySlice_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) PyVarObject_HEAD_INIT(&PyType_Type, 0)
"slice", /* Name of this type */ "slice", /* Name of this type */
@ -642,7 +678,7 @@ PyTypeObject PySlice_Type = {
0, /* tp_as_number */ 0, /* tp_as_number */
0, /* tp_as_sequence */ 0, /* tp_as_sequence */
0, /* tp_as_mapping */ 0, /* tp_as_mapping */
PyObject_HashNotImplemented, /* tp_hash */ (hashfunc)slicehash, /* tp_hash */
0, /* tp_call */ 0, /* tp_call */
0, /* tp_str */ 0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericGetAttr, /* tp_getattro */

View File

@ -288,7 +288,7 @@ error:
/* Hash for tuples. This is a slightly simplified version of the xxHash /* Hash for tuples. This is a slightly simplified version of the xxHash
non-cryptographic hash: 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 - we drop the final mixing since this is just a permutation of the
output space: it does not help against collisions. output space: it does not help against collisions.
- at the end, we mangle the length with a single constant. - at the end, we mangle the length with a single constant.