diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 0b3829f..e767d51 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -425,8 +425,10 @@ loops that truncate the stream. Afterward, elements are returned consecutively unless *step* is set higher than one which results in items being skipped. If *stop* is ``None``, then iteration continues until the iterator is exhausted, if at all; otherwise, it stops at the - specified position. Unlike regular slicing, :func:`islice` does not support - negative values for *start*, *stop*, or *step*. Can be used to extract related + specified position. :func:`islice` does not support negative values for *start* + or *stop* when *iterable* is not sized (no ``__len__()`` method). The negative + values have the same meaning as :func:`range`. However, :func:`islice` + does not support negative value for *step*. Can be used to extract related fields from data where the internal structure has been flattened (for example, a multi-line report may list a name field on every third line). Roughly equivalent to:: diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 4fcc02a..2a91265 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1219,7 +1219,7 @@ class TestBasicOps(unittest.TestCase): self.assertEqual(list(it), list(range(3, 10))) # Test invalid arguments - ra = range(10) + ra = iter(range(10)) self.assertRaises(TypeError, islice, ra) self.assertRaises(TypeError, islice, ra, 1, 2, 3, 4) self.assertRaises(ValueError, islice, ra, -5, 10, 1) @@ -1233,6 +1233,15 @@ class TestBasicOps(unittest.TestCase): self.assertRaises(ValueError, islice, ra, 1, 'a', 1) self.assertEqual(len(list(islice(count(), 1, 10, maxsize))), 1) + # Test negative values for start, stop + dv = {i: i for i in range(0, 10)}.keys() + self.assertEqual(list(islice(dv, -5, 10, 1)), list(range(5, 10))) + self.assertEqual(list(islice(dv, -5)), list(range(5))) + self.assertEqual(list(islice(dv, -5, -1)), list(range(5, 9))) + self.assertEqual(list(islice(dv, -5, -1, 2)), list(range(5, 9, 2))) + self.assertEqual(list(islice(dv, -5, None, 1)), list(range(5, 10))) + self.assertRaises(IndexError, islice, dv, -11) + # Issue #10323: Less islice in a predictable state c = count() self.assertEqual(list(islice(c, 1, 3, 50)), [1]) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index e0810c8..8bb59b0 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -1425,8 +1425,9 @@ islice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) PyObject *seq; Py_ssize_t start=0, stop=-1, step=1; PyObject *it, *a1=NULL, *a2=NULL, *a3=NULL; - Py_ssize_t numargs; + Py_ssize_t numargs, seq_len; isliceobject *lz; + int it_to_last = 0; if (type == &islice_type && !_PyArg_NoKeywords("islice", kwds)) return NULL; @@ -1438,37 +1439,58 @@ islice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (numargs == 2) { if (a1 != Py_None) { stop = PyNumber_AsSsize_t(a1, PyExc_OverflowError); - if (stop == -1) { - if (PyErr_Occurred()) - PyErr_Clear(); + if (stop == -1 && PyErr_Occurred()){ + PyErr_Clear(); PyErr_SetString(PyExc_ValueError, "Stop argument for islice() must be None or " - "an integer: 0 <= x <= sys.maxsize."); + "an integer <= sys.maxsize."); return NULL; } - } + } else + it_to_last = 1; } else { if (a1 != Py_None) start = PyNumber_AsSsize_t(a1, PyExc_OverflowError); - if (start == -1 && PyErr_Occurred()) + if (start == -1 && PyErr_Occurred()) { PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, + "Start argument for islice() must be None or " + "an integer <= sys.maxsize."); + return NULL; + } if (a2 != Py_None) { stop = PyNumber_AsSsize_t(a2, PyExc_OverflowError); - if (stop == -1) { - if (PyErr_Occurred()) - PyErr_Clear(); + if (stop == -1 && PyErr_Occurred()) { + PyErr_Clear(); PyErr_SetString(PyExc_ValueError, "Stop argument for islice() must be None or " - "an integer: 0 <= x <= sys.maxsize."); + "an integer <= sys.maxsize."); return NULL; } - } + } else + it_to_last = 1; } - if (start<0 || stop<-1) { - PyErr_SetString(PyExc_ValueError, - "Indices for islice() must be None or " - "an integer: 0 <= x <= sys.maxsize."); - return NULL; + if (start < 0 || (stop < 0 && !it_to_last)) { + seq_len = PySequence_Size(seq); + if (seq_len == -1) { + if (PyErr_Occurred()) + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, + "Negative values for start and stop arguments " + "are not supported when the iterable argument " + "is not sized."); + return NULL; + } else { + if (start < 0) + start += seq_len; + if (stop < 0 && !it_to_last) + stop += seq_len; + if (start < 0 || (stop < 0 && !it_to_last)) { + PyErr_SetString(PyExc_IndexError, + "islice index out of range"); + return NULL; + } + } } if (a3 != NULL) {