diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 57ba4d45e9b267..17303dd01cef06 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -411,12 +411,24 @@ loops that truncate the stream. # islice('ABCDEFG', 2, None) --> C D E F G # islice('ABCDEFG', 0, None, 2) --> A C E G s = slice(*args) - it = iter(xrange(s.start or 0, s.stop or sys.maxint, s.step or 1)) - nexti = next(it) - for i, element in enumerate(iterable): - if i == nexti: - yield element - nexti = next(it) + start, stop, step = s.start or 0, s.stop or sys.maxint, s.step or 1 + it = iter(xrange(start, stop, step))) + try: + nexti = next(it) + except StopIteration: + # Consume *iterable* up to the *start* position. + for i, element in izip(xrange(start), iterable): + pass + return + try: + for i, element in enumerate(iterable): + if i == nexti: + yield element + nexti = next(it) + except StopIteration: + # Consume to *stop*. + for i, element in izip(xrange(i + 1, stop), iterable): + pass If *start* is ``None``, then iteration starts at zero. If *step* is ``None``, then the step defaults to one. @@ -681,8 +693,8 @@ which incur interpreter overhead. "Return function(0), function(1), ..." return imap(function, count(start)) - def consume(iterator, n): - "Advance the iterator n-steps ahead. If n is none, consume entirely." + def consume(iterator, n=None): + "Advance the iterator n-steps ahead. If n is None, consume entirely." # Use functions that consume iterators at C speed. if n is None: # feed the entire iterator into a zero-length deque diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 1e6db3426e4f22..04279792065edf 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -802,6 +802,7 @@ def test_islice(self): (10, 20, 3), (10, 3, 20), (10, 20), + (10, 10), (10, 3), (20,) ]: @@ -826,6 +827,10 @@ def test_islice(self): self.assertEqual(list(islice(it, 3)), range(3)) self.assertEqual(list(it), range(3, 10)) + it = iter(range(10)) + self.assertEqual(list(islice(it, 3, 3)), []) + self.assertEqual(list(it), range(3, 10)) + # Test invalid arguments self.assertRaises(TypeError, islice, xrange(10)) self.assertRaises(TypeError, islice, xrange(10), 1, 2, 3, 4) @@ -1084,6 +1089,48 @@ def test_takewhile(self): self.assertEqual(list(takewhile(lambda x: x<5, [1,4,6,4,1])), [1,4]) +class TestPurePythonRoughEquivalents(unittest.TestCase): + + @staticmethod + def islice(iterable, *args): + s = slice(*args) + start, stop, step = s.start or 0, s.stop or sys.maxint, s.step or 1 + it = iter(xrange(start, stop, step)) + try: + nexti = next(it) + except StopIteration: + # Consume *iterable* up to the *start* position. + for i, element in izip(xrange(start), iterable): + pass + return + try: + for i, element in enumerate(iterable): + if i == nexti: + yield element + nexti = next(it) + except StopIteration: + # Consume to *stop*. + for i, element in izip(xrange(i + 1, stop), iterable): + pass + + def test_islice_recipe(self): + self.assertEqual(list(self.islice('ABCDEFG', 2)), list('AB')) + self.assertEqual(list(self.islice('ABCDEFG', 2, 4)), list('CD')) + self.assertEqual(list(self.islice('ABCDEFG', 2, None)), list('CDEFG')) + self.assertEqual(list(self.islice('ABCDEFG', 0, None, 2)), list('ACEG')) + # Test items consumed. + it = iter(xrange(10)) + self.assertEqual(list(self.islice(it, 3)), range(3)) + self.assertEqual(list(it), range(3, 10)) + it = iter(xrange(10)) + self.assertEqual(list(self.islice(it, 3, 3)), []) + self.assertEqual(list(it), range(3, 10)) + # Test that slice finishes in predictable state. + c = count() + self.assertEqual(list(self.islice(c, 1, 3, 50)), [1]) + self.assertEqual(next(c), 3) + + class TestGC(unittest.TestCase): def makecycle(self, iterator, container): @@ -1577,6 +1624,17 @@ def __init__(self, newarg=None, *args): ... "Return function(0), function(1), ..." ... return imap(function, count(start)) +>>> import collections +>>> def consume(iterator, n=None): +... "Advance the iterator n-steps ahead. If n is None, consume entirely." +... # Use functions that consume iterators at C speed. +... if n is None: +... # feed the entire iterator into a zero-length deque +... collections.deque(iterator, maxlen=0) +... else: +... # advance to the empty slice starting at position n +... next(islice(iterator, n, n), None) + >>> def nth(iterable, n, default=None): ... "Returns the nth item or a default value" ... return next(islice(iterable, n, None), default) @@ -1678,6 +1736,14 @@ def __init__(self, newarg=None, *args): >>> list(islice(tabulate(lambda x: 2*x), 4)) [0, 2, 4, 6] +>>> it = iter(xrange(10)) +>>> consume(it, 3) +>>> next(it) +3 +>>> consume(it) +>>> next(it, 'Done') +'Done' + >>> nth('abcde', 3) 'd' @@ -1753,7 +1819,8 @@ def __init__(self, newarg=None, *args): def test_main(verbose=None): test_classes = (TestBasicOps, TestVariousIteratorArgs, TestGC, RegressionTests, LengthTransparency, - SubclassWithKwargsTest, TestExamples) + SubclassWithKwargsTest, TestExamples, + TestPurePythonRoughEquivalents) test_support.run_unittest(*test_classes) # verify reference counting diff --git a/Misc/NEWS.d/next/Documentation/2018-03-22-19-23-04.bpo-27212.wrE5KR.rst b/Misc/NEWS.d/next/Documentation/2018-03-22-19-23-04.bpo-27212.wrE5KR.rst new file mode 100644 index 00000000000000..5910d2c17342f5 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2018-03-22-19-23-04.bpo-27212.wrE5KR.rst @@ -0,0 +1,2 @@ +Modify documentation for the :func:`islice` recipe to consume initial values +up to the start index.