|
4 | 4 | from test import support |
5 | 5 | from pickle import loads, dumps |
6 | 6 |
|
| 7 | +import itertools |
| 8 | +import operator |
7 | 9 | import sys |
8 | 10 |
|
| 11 | + |
| 12 | +def evaluate_slice_index(arg): |
| 13 | + """ |
| 14 | + Helper function to convert a slice argument to an integer, and raise |
| 15 | + TypeError with a suitable message on failure. |
| 16 | +
|
| 17 | + """ |
| 18 | + if hasattr(arg, '__index__'): |
| 19 | + return operator.index(arg) |
| 20 | + else: |
| 21 | + raise TypeError( |
| 22 | + "slice indices must be integers or " |
| 23 | + "None or have an __index__ method") |
| 24 | + |
| 25 | +def slice_indices(slice, length): |
| 26 | + """ |
| 27 | + Reference implementation for the slice.indices method. |
| 28 | +
|
| 29 | + """ |
| 30 | + # Compute step and length as integers. |
| 31 | + length = operator.index(length) |
| 32 | + step = 1 if slice.step is None else evaluate_slice_index(slice.step) |
| 33 | + |
| 34 | + # Raise ValueError for negative length or zero step. |
| 35 | + if length < 0: |
| 36 | + raise ValueError("length should not be negative") |
| 37 | + if step == 0: |
| 38 | + raise ValueError("slice step cannot be zero") |
| 39 | + |
| 40 | + # Find lower and upper bounds for start and stop. |
| 41 | + lower = -1 if step < 0 else 0 |
| 42 | + upper = length - 1 if step < 0 else length |
| 43 | + |
| 44 | + # Compute start. |
| 45 | + if slice.start is None: |
| 46 | + start = upper if step < 0 else lower |
| 47 | + else: |
| 48 | + start = evaluate_slice_index(slice.start) |
| 49 | + start = max(start + length, lower) if start < 0 else min(start, upper) |
| 50 | + |
| 51 | + # Compute stop. |
| 52 | + if slice.stop is None: |
| 53 | + stop = lower if step < 0 else upper |
| 54 | + else: |
| 55 | + stop = evaluate_slice_index(slice.stop) |
| 56 | + stop = max(stop + length, lower) if stop < 0 else min(stop, upper) |
| 57 | + |
| 58 | + return start, stop, step |
| 59 | + |
| 60 | + |
| 61 | +# Class providing an __index__ method. Used for testing slice.indices. |
| 62 | + |
| 63 | +class MyIndexable(object): |
| 64 | + def __init__(self, value): |
| 65 | + self.value = value |
| 66 | + |
| 67 | + def __index__(self): |
| 68 | + return self.value |
| 69 | + |
| 70 | + |
9 | 71 | class SliceTest(unittest.TestCase): |
10 | 72 |
|
11 | 73 | def test_constructor(self): |
@@ -75,6 +137,22 @@ class AnyClass: |
75 | 137 | s = slice(obj) |
76 | 138 | self.assertTrue(s.stop is obj) |
77 | 139 |
|
| 140 | + def check_indices(self, slice, length): |
| 141 | + try: |
| 142 | + actual = slice.indices(length) |
| 143 | + except ValueError: |
| 144 | + actual = "valueerror" |
| 145 | + try: |
| 146 | + expected = slice_indices(slice, length) |
| 147 | + except ValueError: |
| 148 | + expected = "valueerror" |
| 149 | + self.assertEqual(actual, expected) |
| 150 | + |
| 151 | + if length >= 0 and slice.step != 0: |
| 152 | + actual = range(*slice.indices(length)) |
| 153 | + expected = range(length)[slice] |
| 154 | + self.assertEqual(actual, expected) |
| 155 | + |
78 | 156 | def test_indices(self): |
79 | 157 | self.assertEqual(slice(None ).indices(10), (0, 10, 1)) |
80 | 158 | self.assertEqual(slice(None, None, 2).indices(10), (0, 10, 2)) |
@@ -108,7 +186,41 @@ def test_indices(self): |
108 | 186 |
|
109 | 187 | self.assertEqual(list(range(10))[::sys.maxsize - 1], [0]) |
110 | 188 |
|
111 | | - self.assertRaises(OverflowError, slice(None).indices, 1<<100) |
| 189 | + # Check a variety of start, stop, step and length values, including |
| 190 | + # values exceeding sys.maxsize (see issue #14794). |
| 191 | + vals = [None, -2**100, -2**30, -53, -7, -1, 0, 1, 7, 53, 2**30, 2**100] |
| 192 | + lengths = [0, 1, 7, 53, 2**30, 2**100] |
| 193 | + for slice_args in itertools.product(vals, repeat=3): |
| 194 | + s = slice(*slice_args) |
| 195 | + for length in lengths: |
| 196 | + self.check_indices(s, length) |
| 197 | + self.check_indices(slice(0, 10, 1), -3) |
| 198 | + |
| 199 | + # Negative length should raise ValueError |
| 200 | + with self.assertRaises(ValueError): |
| 201 | + slice(None).indices(-1) |
| 202 | + |
| 203 | + # Zero step should raise ValueError |
| 204 | + with self.assertRaises(ValueError): |
| 205 | + slice(0, 10, 0).indices(5) |
| 206 | + |
| 207 | + # Using a start, stop or step or length that can't be interpreted as an |
| 208 | + # integer should give a TypeError ... |
| 209 | + with self.assertRaises(TypeError): |
| 210 | + slice(0.0, 10, 1).indices(5) |
| 211 | + with self.assertRaises(TypeError): |
| 212 | + slice(0, 10.0, 1).indices(5) |
| 213 | + with self.assertRaises(TypeError): |
| 214 | + slice(0, 10, 1.0).indices(5) |
| 215 | + with self.assertRaises(TypeError): |
| 216 | + slice(0, 10, 1).indices(5.0) |
| 217 | + |
| 218 | + # ... but it should be fine to use a custom class that provides index. |
| 219 | + self.assertEqual(slice(0, 10, 1).indices(5), (0, 5, 1)) |
| 220 | + self.assertEqual(slice(MyIndexable(0), 10, 1).indices(5), (0, 5, 1)) |
| 221 | + self.assertEqual(slice(0, MyIndexable(10), 1).indices(5), (0, 5, 1)) |
| 222 | + self.assertEqual(slice(0, 10, MyIndexable(1)).indices(5), (0, 5, 1)) |
| 223 | + self.assertEqual(slice(0, 10, 1).indices(MyIndexable(5)), (0, 5, 1)) |
112 | 224 |
|
113 | 225 | def test_setslice_without_getslice(self): |
114 | 226 | tmp = [] |
|
0 commit comments