Skip to content

Commit da1734c

Browse files
csabellarhettinger
authored andcommitted
bpo-27212: Modify islice recipe to consume initial values preceding start (GH-6195)
1 parent 8349403 commit da1734c

File tree

3 files changed

+85
-7
lines changed

3 files changed

+85
-7
lines changed

Doc/library/itertools.rst

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,15 +436,24 @@ loops that truncate the stream.
436436
# islice('ABCDEFG', 2, None) --> C D E F G
437437
# islice('ABCDEFG', 0, None, 2) --> A C E G
438438
s = slice(*args)
439-
it = iter(range(s.start or 0, s.stop or sys.maxsize, s.step or 1))
439+
start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
440+
it = iter(range(start, stop, step))
440441
try:
441442
nexti = next(it)
442443
except StopIteration:
444+
# Consume *iterable* up to the *start* position.
445+
for i, element in zip(range(start), iterable):
446+
pass
443447
return
444-
for i, element in enumerate(iterable):
445-
if i == nexti:
446-
yield element
447-
nexti = next(it)
448+
try:
449+
for i, element in enumerate(iterable):
450+
if i == nexti:
451+
yield element
452+
nexti = next(it)
453+
except StopIteration:
454+
# Consume to *stop*.
455+
for i, element in zip(range(i + 1, stop), iterable):
456+
pass
448457

449458
If *start* is ``None``, then iteration starts at zero. If *step* is ``None``,
450459
then the step defaults to one.
@@ -688,8 +697,8 @@ which incur interpreter overhead.
688697
# tail(3, 'ABCDEFG') --> E F G
689698
return iter(collections.deque(iterable, maxlen=n))
690699

691-
def consume(iterator, n):
692-
"Advance the iterator n-steps ahead. If n is none, consume entirely."
700+
def consume(iterator, n=None):
701+
"Advance the iterator n-steps ahead. If n is None, consume entirely."
693702
# Use functions that consume iterators at C speed.
694703
if n is None:
695704
# feed the entire iterator into a zero-length deque

Lib/test/test_itertools.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,7 @@ def test_islice(self):
11921192
(10, 20, 3),
11931193
(10, 3, 20),
11941194
(10, 20),
1195+
(10, 10),
11951196
(10, 3),
11961197
(20,)
11971198
]:
@@ -1218,6 +1219,10 @@ def test_islice(self):
12181219
self.assertEqual(list(islice(it, 3)), list(range(3)))
12191220
self.assertEqual(list(it), list(range(3, 10)))
12201221

1222+
it = iter(range(10))
1223+
self.assertEqual(list(islice(it, 3, 3)), [])
1224+
self.assertEqual(list(it), list(range(3, 10)))
1225+
12211226
# Test invalid arguments
12221227
ra = range(10)
12231228
self.assertRaises(TypeError, islice, ra)
@@ -1604,6 +1609,48 @@ def test_takewhile(self):
16041609
self.assertEqual(list(takewhile(lambda x: x<5, [1,4,6,4,1])), [1,4])
16051610

16061611

1612+
class TestPurePythonRoughEquivalents(unittest.TestCase):
1613+
1614+
@staticmethod
1615+
def islice(iterable, *args):
1616+
s = slice(*args)
1617+
start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
1618+
it = iter(range(start, stop, step))
1619+
try:
1620+
nexti = next(it)
1621+
except StopIteration:
1622+
# Consume *iterable* up to the *start* position.
1623+
for i, element in zip(range(start), iterable):
1624+
pass
1625+
return
1626+
try:
1627+
for i, element in enumerate(iterable):
1628+
if i == nexti:
1629+
yield element
1630+
nexti = next(it)
1631+
except StopIteration:
1632+
# Consume to *stop*.
1633+
for i, element in zip(range(i + 1, stop), iterable):
1634+
pass
1635+
1636+
def test_islice_recipe(self):
1637+
self.assertEqual(list(self.islice('ABCDEFG', 2)), list('AB'))
1638+
self.assertEqual(list(self.islice('ABCDEFG', 2, 4)), list('CD'))
1639+
self.assertEqual(list(self.islice('ABCDEFG', 2, None)), list('CDEFG'))
1640+
self.assertEqual(list(self.islice('ABCDEFG', 0, None, 2)), list('ACEG'))
1641+
# Test items consumed.
1642+
it = iter(range(10))
1643+
self.assertEqual(list(self.islice(it, 3)), list(range(3)))
1644+
self.assertEqual(list(it), list(range(3, 10)))
1645+
it = iter(range(10))
1646+
self.assertEqual(list(self.islice(it, 3, 3)), [])
1647+
self.assertEqual(list(it), list(range(3, 10)))
1648+
# Test that slice finishes in predictable state.
1649+
c = count()
1650+
self.assertEqual(list(self.islice(c, 1, 3, 50)), [1])
1651+
self.assertEqual(next(c), 3)
1652+
1653+
16071654
class TestGC(unittest.TestCase):
16081655

16091656
def makecycle(self, iterator, container):
@@ -2158,6 +2205,17 @@ def test_permutations_sizeof(self):
21582205
... "Return function(0), function(1), ..."
21592206
... return map(function, count(start))
21602207
2208+
>>> import collections
2209+
>>> def consume(iterator, n=None):
2210+
... "Advance the iterator n-steps ahead. If n is None, consume entirely."
2211+
... # Use functions that consume iterators at C speed.
2212+
... if n is None:
2213+
... # feed the entire iterator into a zero-length deque
2214+
... collections.deque(iterator, maxlen=0)
2215+
... else:
2216+
... # advance to the empty slice starting at position n
2217+
... next(islice(iterator, n, n), None)
2218+
21612219
>>> def nth(iterable, n, default=None):
21622220
... "Returns the nth item or a default value"
21632221
... return next(islice(iterable, n, None), default)
@@ -2298,6 +2356,14 @@ def test_permutations_sizeof(self):
22982356
>>> list(islice(tabulate(lambda x: 2*x), 4))
22992357
[0, 2, 4, 6]
23002358
2359+
>>> it = iter(range(10))
2360+
>>> consume(it, 3)
2361+
>>> next(it)
2362+
3
2363+
>>> consume(it)
2364+
>>> next(it, 'Done')
2365+
'Done'
2366+
23012367
>>> nth('abcde', 3)
23022368
'd'
23032369
@@ -2386,6 +2452,7 @@ def test_main(verbose=None):
23862452
test_classes = (TestBasicOps, TestVariousIteratorArgs, TestGC,
23872453
RegressionTests, LengthTransparency,
23882454
SubclassWithKwargsTest, TestExamples,
2455+
TestPurePythonRoughEquivalents,
23892456
SizeofTest)
23902457
support.run_unittest(*test_classes)
23912458

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Modify documentation for the :func:`islice` recipe to consume initial values
2+
up to the start index.

0 commit comments

Comments
 (0)