]> granicus.if.org Git - python/commitdiff
SF bug #730685: itertools.islice stop argument is not optional
authorRaymond Hettinger <python@rcn.com>
Fri, 2 May 2003 19:04:37 +0000 (19:04 +0000)
committerRaymond Hettinger <python@rcn.com>
Fri, 2 May 2003 19:04:37 +0000 (19:04 +0000)
* itertools.islice() stop argument did not perform as documented.
* beefed-up test suite

Doc/lib/libitertools.tex
Lib/test/test_itertools.py
Modules/itertoolsmodule.c

index 93116ea04219a97ef601db548fa5bb27dea5f8eb..d0e12696c169051145cb0df176451fb667a48550 100644 (file)
@@ -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"
index 2a6095970c32005592cb9b577b45808ad1aae478..d0b1ce80b9ed2c43ab70422cbd7f21fc78208cc8 100644 (file)
@@ -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)
index 35fa1d07da92329fc940ff42faf07ae93cb14636..f05ebd6beebabcaeb0f9032955842726497bfce8 100644 (file)
@@ -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);