From 0f51d3de1c0921c7d7cb2bdccfe59704a638c354 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Thu, 22 Mar 2018 19:24:24 -0400 Subject: [PATCH 1/4] bpo-27212: Modify islice recipe to consume initial values --- Doc/library/itertools.rst | 7 ++++++- .../Documentation/2018-03-22-19-23-04.bpo-27212.wrE5KR.rst | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Documentation/2018-03-22-19-23-04.bpo-27212.wrE5KR.rst diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 0b3829f19faf9c..c37806d836be6c 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -435,8 +435,13 @@ loops that truncate the stream. # islice('ABCDEFG', 2, 4) --> C D # islice('ABCDEFG', 2, None) --> C D E F G # islice('ABCDEFG', 0, None, 2) --> A C E G + # it = iter('abcdefghi') + # list(islice(it, 4, 4)) --> [] + # list(it) --> ['e', 'f', 'g', 'h', 'i'] s = slice(*args) - it = iter(range(s.start or 0, s.stop or sys.maxsize, s.step or 1)) + it = iter(range(0, s.stop or sys.maxsize, s.step or 1)) + for i in zip(range(0, s.start or 0), iterable): + nexti = next(it) try: nexti = next(it) except StopIteration: 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..48b586fe6f0ed4 --- /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 start. From 93504c6f34ebcd84d28e7bd06c58bd74dd25362e Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 24 Mar 2018 19:37:05 -0400 Subject: [PATCH 2/4] Requested changes and tests. --- Doc/library/itertools.rst | 23 ++++++++----- Lib/test/test_itertools.py | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index c37806d836be6c..131a783c624f2b 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -435,21 +435,26 @@ loops that truncate the stream. # islice('ABCDEFG', 2, 4) --> C D # islice('ABCDEFG', 2, None) --> C D E F G # islice('ABCDEFG', 0, None, 2) --> A C E G - # it = iter('abcdefghi') - # list(islice(it, 4, 4)) --> [] - # list(it) --> ['e', 'f', 'g', 'h', 'i'] s = slice(*args) - it = iter(range(0, s.stop or sys.maxsize, s.step or 1)) - for i in zip(range(0, s.start or 0), iterable): - nexti = next(it) + start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1 + it = iter(range(start, stop, step)) try: nexti = next(it) except StopIteration: + # Consume *iterable* up to the *start* position. + for i, element in zip(range(start), iterable): + pass return for i, element in enumerate(iterable): if i == nexti: yield element - nexti = next(it) + try: + nexti = next(it) + except StopIteration: + # Consume to *stop*. + for i, element in zip(range(i + 1, stop), iterable): + pass + return If *start* is ``None``, then iteration starts at zero. If *step* is ``None``, then the step defaults to one. @@ -693,8 +698,8 @@ which incur interpreter overhead. # tail(3, 'ABCDEFG') --> E F G return iter(collections.deque(iterable, maxlen=n)) - 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 4fcc02acbf441c..a3eae667b64fc1 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1192,6 +1192,7 @@ def test_islice(self): (10, 20, 3), (10, 3, 20), (10, 20), + (10, 10), (10, 3), (20,) ]: @@ -1218,6 +1219,10 @@ def test_islice(self): self.assertEqual(list(islice(it, 3)), list(range(3))) self.assertEqual(list(it), list(range(3, 10))) + it = iter(range(10)) + self.assertEqual(list(islice(it, 3, 3)), []) + self.assertEqual(list(it), list(range(3, 10))) + # Test invalid arguments ra = range(10) self.assertRaises(TypeError, islice, ra) @@ -1604,6 +1609,49 @@ 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.maxsize, s.step or 1 + it = iter(range(start, stop, step)) + try: + nexti = next(it) + except StopIteration: + # Consume *iterable* up to the *start* position. + for i, element in zip(range(start), iterable): + pass + return + for i, element in enumerate(iterable): + if i == nexti: + yield element + try: + nexti = next(it) + except StopIteration: + # Consume to *stop*. + for i, element in zip(range(i + 1, stop), iterable): + pass + return + + 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(range(10)) + self.assertEqual(list(self.islice(it, 3)), list(range(3))) + self.assertEqual(list(it), list(range(3, 10))) + it = iter(range(10)) + self.assertEqual(list(self.islice(it, 3, 3)), []) + self.assertEqual(list(it), list(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): @@ -2158,6 +2206,17 @@ def test_permutations_sizeof(self): ... "Return function(0), function(1), ..." ... return map(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) @@ -2298,6 +2357,14 @@ def test_permutations_sizeof(self): >>> list(islice(tabulate(lambda x: 2*x), 4)) [0, 2, 4, 6] +>>> it = iter(range(10)) +>>> consume(it, 3) +>>> next(it) +3 +>>> consume(it) +>>> next(it, 'Done') +'Done' + >>> nth('abcde', 3) 'd' @@ -2386,6 +2453,7 @@ def test_main(verbose=None): test_classes = (TestBasicOps, TestVariousIteratorArgs, TestGC, RegressionTests, LengthTransparency, SubclassWithKwargsTest, TestExamples, + TestPurePythonRoughEquivalents, SizeofTest) support.run_unittest(*test_classes) From e73983d933ce97f565ec1d7d2a53ffa1288b6429 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sun, 25 Mar 2018 14:37:24 -0400 Subject: [PATCH 3/4] Move try block and update blurb --- Doc/library/itertools.rst | 18 +++++++++--------- Lib/test/test_itertools.py | 18 +++++++++--------- .../2018-03-22-19-23-04.bpo-27212.wrE5KR.rst | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 131a783c624f2b..31494a1d039e3d 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -445,16 +445,16 @@ loops that truncate the stream. for i, element in zip(range(start), iterable): pass return - for i, element in enumerate(iterable): - if i == nexti: - yield element - try: + try: + for i, element in enumerate(iterable): + if i == nexti: + yield element nexti = next(it) - except StopIteration: - # Consume to *stop*. - for i, element in zip(range(i + 1, stop), iterable): - pass - return + except StopIteration: + # Consume to *stop*. + for i, element in zip(range(i + 1, stop), iterable): + pass + return If *start* is ``None``, then iteration starts at zero. If *step* is ``None``, then the step defaults to one. diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index a3eae667b64fc1..1fc2bd2be47c09 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1623,16 +1623,16 @@ def islice(iterable, *args): for i, element in zip(range(start), iterable): pass return - for i, element in enumerate(iterable): - if i == nexti: - yield element - try: + try: + for i, element in enumerate(iterable): + if i == nexti: + yield element nexti = next(it) - except StopIteration: - # Consume to *stop*. - for i, element in zip(range(i + 1, stop), iterable): - pass - return + except StopIteration: + # Consume to *stop*. + for i, element in zip(range(i + 1, stop), iterable): + pass + return def test_islice_recipe(self): self.assertEqual(list(self.islice('ABCDEFG', 2)), list('AB')) 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 index 48b586fe6f0ed4..5910d2c17342f5 100644 --- 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 @@ -1,2 +1,2 @@ Modify documentation for the :func:`islice` recipe to consume initial values -up to start. +up to the start index. From 6a064a3e2fa15fb473514dfd727e1270612dc240 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Mon, 26 Mar 2018 10:25:59 -0400 Subject: [PATCH 4/4] Remove unneeded return --- Doc/library/itertools.rst | 1 - Lib/test/test_itertools.py | 1 - 2 files changed, 2 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 31494a1d039e3d..a5a5356a9a1fa4 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -454,7 +454,6 @@ loops that truncate the stream. # Consume to *stop*. for i, element in zip(range(i + 1, stop), iterable): pass - return If *start* is ``None``, then iteration starts at zero. If *step* is ``None``, then the step defaults to one. diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 1fc2bd2be47c09..effd7f0e21bec5 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1632,7 +1632,6 @@ def islice(iterable, *args): # Consume to *stop*. for i, element in zip(range(i + 1, stop), iterable): pass - return def test_islice_recipe(self): self.assertEqual(list(self.islice('ABCDEFG', 2)), list('AB'))