SF bug #730685: itertools.islice stop argument is not optional
* itertools.islice() stop argument did not perform as documented. * beefed-up test suite
This commit is contained in:
parent
e2df5ffa53
commit
14ef54cd83
|
@ -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,14 +208,17 @@ by functions or loops that truncate the stream.
|
|||
|
||||
\begin{verbatim}
|
||||
def islice(iterable, *args):
|
||||
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
|
||||
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue