From 14ef54cd833c7ccfa0b3d03e2b45074d54a8ac87 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 2 May 2003 19:04:37 +0000 Subject: SF bug #730685: itertools.islice stop argument is not optional * itertools.islice() stop argument did not perform as documented. * beefed-up test suite --- Doc/lib/libitertools.tex | 23 +++++++------- Lib/test/test_itertools.py | 74 ++++++++++++++++++++++++++++++++++++++++++++-- Modules/itertoolsmodule.c | 46 ++++++++++++++++++++-------- 3 files changed, 117 insertions(+), 26 deletions(-) diff --git a/Doc/lib/libitertools.tex b/Doc/lib/libitertools.tex index 93116ea..d0e1269 100644 --- a/Doc/lib/libitertools.tex +++ b/Doc/lib/libitertools.tex @@ -197,9 +197,9 @@ by functions or loops that truncate the stream. If \var{start} is non-zero, then elements from the iterable are skipped until start is reached. Afterward, elements are returned consecutively unless \var{step} is set higher than one which results in items being - skipped. If \var{stop} is specified, then iteration stops at the - specified element position; otherwise, it continues indefinitely or - until the iterable is exhausted. Unlike regular slicing, + skipped. If \var{stop} is not specified or is \code{None}, then iteration + continues indefinitely; otherwise, it stops at the specified position. + Unlike regular slicing, \function{islice()} does not support negative values for \var{start}, \var{stop}, or \var{step}. Can be used to extract related fields from data where the internal structure has been flattened (for @@ -208,17 +208,20 @@ by functions or loops that truncate the stream. \begin{verbatim} def islice(iterable, *args): - s = slice(*args) - next = s.start or 0 - stop = s.stop - step = s.step or 1 + if args: + s = slice(*args) + next = s.start or 0 + stop = s.stop + step = s.step or 1 + else: + next, stop, step = 0, None, 1 for cnt, element in enumerate(iterable): if cnt < next: continue - if cnt >= stop: + if stop is not None and cnt >= stop: break yield element - next += step + next += step \end{verbatim} \end{funcdesc} @@ -360,7 +363,7 @@ from building blocks. >>> def pairwise(seq): ... "s -> (s0,s1), (s1,s2), (s2, s3), ..." -... return izip(seq, islice(seq,1,len(seq))) +... return izip(seq, islice(seq,1,None)) >>> def padnone(seq): ... "Returns the sequence elements and then returns None indefinitely" diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 2a60959..d0b1ce8 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -77,12 +77,23 @@ class TestBasicOps(unittest.TestCase): ]: self.assertEqual(list(islice(xrange(100), *args)), range(*tgtargs)) - self.assertRaises(TypeError, islice, xrange(10)) + # Test stop=None + self.assertEqual(list(islice(xrange(10))), range(10)) + self.assertEqual(list(islice(xrange(10), None)), range(10)) + self.assertEqual(list(islice(xrange(10), 2, None)), range(2, 10)) + self.assertEqual(list(islice(xrange(10), 1, None, 2)), range(1, 10, 2)) + + # Test invalid arguments self.assertRaises(TypeError, islice, xrange(10), 1, 2, 3, 4) self.assertRaises(ValueError, islice, xrange(10), -5, 10, 1) self.assertRaises(ValueError, islice, xrange(10), 1, -5, -1) self.assertRaises(ValueError, islice, xrange(10), 1, 10, -1) self.assertRaises(ValueError, islice, xrange(10), 1, 10, 0) + self.assertRaises(ValueError, islice, xrange(10), 'a') + self.assertRaises(ValueError, islice, xrange(10), 'a', 1) + self.assertRaises(ValueError, islice, xrange(10), 1, 'a') + self.assertRaises(ValueError, islice, xrange(10), 'a', 1, 1) + self.assertRaises(ValueError, islice, xrange(10), 1, 'a', 1) self.assertEqual(len(list(islice(count(), 1, 10, sys.maxint))), 1) def test_takewhile(self): @@ -155,16 +166,69 @@ Samuele ... "s -> (s0,s1), (s1,s2), (s2, s3), ..." ... return izip(seq, islice(seq,1,len(seq))) +>>> def padnone(seq): +... "Returns the sequence elements and then returns None indefinitely" +... return chain(seq, repeat(None)) + +>>> def ncycles(seq, n): +... "Returns the sequence elements n times" +... return chain(*repeat(seq, n)) + +>>> def dotproduct(vec1, vec2): +... return sum(imap(operator.mul, vec1, vec2)) + + +This is not part of the examples but it tests to make sure the definitions +perform as purported. + +>>> list(enumerate('abc')) +[(0, 'a'), (1, 'b'), (2, 'c')] + +>>> list(islice(tabulate(lambda x: 2*x), 4)) +[0, 2, 4, 6] + +>>> nth('abcde', 3) +['d'] + +>>> all(lambda x: x%2==0, [2, 4, 6, 8]) +True + +>>> all(lambda x: x%2==0, [2, 3, 6, 8]) +False + +>>> some(lambda x: x%2==0, [2, 4, 6, 8]) +True + +>>> some(lambda x: x%2==0, [1, 3, 5, 9]) +False + +>>> no(lambda x: x%2==0, [1, 3, 5, 9]) +True + +>>> no(lambda x: x%2==0, [1, 2, 5, 9]) +False + +>>> list(pairwise('abc')) +[('a', 'b'), ('b', 'c')] + +>>> list(islice(padnone('abc'), 0, 6)) +['a', 'b', 'c', None, None, None] + +>>> list(ncycles('abc', 3)) +['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] + +>>> dotproduct([1,2,3], [4,5,6]) +32 + + """ __test__ = {'libreftest' : libreftest} def test_main(verbose=None): - import test_itertools suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestBasicOps)) test_support.run_suite(suite) - test_support.run_doctest(test_itertools, verbose) # verify reference counting import sys @@ -175,5 +239,9 @@ def test_main(verbose=None): counts.append(sys.gettotalrefcount()-i) print counts + # doctest the examples in the library reference + import doctest + doctest.testmod(sys.modules[__name__]) + if __name__ == "__main__": test_main(verbose=True) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 35fa1d0..f05ebd6 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -471,27 +471,47 @@ static PyObject * islice_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *seq; - long a1=0, a2=0, a3=0, start=0, stop=0, step=1; - PyObject *it; + long start=0, stop=-1, step=1; + PyObject *it, *a1=NULL, *a2=NULL; int numargs; isliceobject *lz; numargs = PyTuple_Size(args); - if (!PyArg_ParseTuple(args, "Ol|ll:islice", &seq, &a1, &a2, &a3)) + if (!PyArg_ParseTuple(args, "O|OOl:islice", &seq, &a1, &a2, &step)) return NULL; if (numargs == 2) { - stop = a1; - } else if (numargs == 3) { - start = a1; - stop = a2; - } else { - start = a1; - stop = a2; - step = a3; + if (a1 != Py_None) { + stop = PyInt_AsLong(a1); + if (stop == -1) { + if (PyErr_Occurred()) + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, + "Stop argument must be an integer or None."); + return NULL; + } + } + } else if (numargs == 3 || numargs == 4) { + start = PyInt_AsLong(a1); + if (start == -1 && PyErr_Occurred()) { + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, + "Start argument must be an integer."); + return NULL; + } + if (a2 != Py_None) { + stop = PyInt_AsLong(a2); + if (stop == -1) { + if (PyErr_Occurred()) + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, + "Stop argument must be an integer or None."); + return NULL; + } + } } - if (start<0 || stop<0) { + if (start<0 || stop<-1) { PyErr_SetString(PyExc_ValueError, "Indices for islice() must be positive."); return NULL; @@ -554,7 +574,7 @@ islice_next(isliceobject *lz) Py_DECREF(item); lz->cnt++; } - if (lz->cnt >= lz->stop) + if (lz->stop != -1 && lz->cnt >= lz->stop) return NULL; assert(PyIter_Check(it)); item = (*it->ob_type->tp_iternext)(it); -- cgit v0.12