8000 bpo-27212: Modify islice recipe to consume initial values preceding s… · python/cpython@c8698cf · GitHub
[go: up one dir, main page]

Skip to content

Commit c8698cf

Browse files
miss-islingtoncsabella
authored andcommitted
bpo-27212: Modify islice recipe to consume initial values preceding start (GH-6195) (GH-6267)
(cherry picked from commit da1734c) Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
1 parent 57db13e commit c8698cf

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
@@ -435,15 +435,24 @@ loops that truncate the stream.
435435
# islice('ABCDEFG', 2, None) --> C D E F G
436436
# islice('ABCDEFG', 0, None, 2) --> A C E G
437437
s = slice(*args)
438-
it = iter(range(s.start or 0, s.stop or sys.maxsize, s.step or 1))
438+
start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
439+
it = iter(range(start, stop, step))
439440
try:
440441
nexti = next(it)
441442
except StopIteration:
443+
# Consume *iterable* up to the *start* position.
444+
for i, element in zip(range(start), iterable):
445+
pass
442446
return
443-
for i, element in enumerate(iterable):
444-
if i == nexti:
445-
yield element
446-
nexti = next(it)
447+
try:
448+
for i, element in enumerate(iterable):
449+
if i == nexti:
450+
yield element
451+
nexti = next(it)
452+
except StopIteration:
453+
# Consume to *stop*.
454+
for i, element in zip(range(i + 1, stop), iterable):
455+
pass
447456

448457
If *start* is ``None``, then iteration starts at zero. If *step* is ``None``,
449458
then the step defaults to one.
@@ -688,8 +697,8 @@ whi 10000 ch 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
@@ -1172,6 +1172,7 @@ def test_islice(self):
11721172
(10, 20, 3),
11731173
(10, 3, 20),
11741174
(10, 20),
1175+
(10, 10),
11751176
(10, 3),
11761177
(20,)
11771178
]:
@@ -1198,6 +1199,10 @@ def test_islice(self):
11981199
self.assertEqual(list(islice(it, 3)), list(range(3)))
11991200
self.assertEqual(list(it), list(range(3, 10)))
12001201

1202+
it = iter(range(10))
1203+
self.assertEqual(list(islice(it, 3, 3)), [])
1204+
self.assertEqual(list(it), list(range(3, 10)))
1205+
12011206
# Test invalid arguments
12021207
ra = range(10)
12031208
self.assertRaises(TypeError, islice, ra)
@@ -1571,6 +1576,48 @@ def test_takewhile(self):
15711576
self.assertEqual(list(takewhile(lambda x: x<5, [1,4,6,4,1])), [1,4])
15721577

15731578

1579+
class TestPurePythonRoughEquivalents(unittest.TestCase):
1580+
1581+
@staticmethod
1582+
def islice(iterable, *args):
1583+
s = slice(*args)
1584+
start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
1585+
it = iter(range(start, stop, step))
1586+
try:
1587+
nexti = next(it)
1588+
except StopIteration:
1589+
# Consume *iterable* up to the *start* position.
1590+
for i, element in zip(range(start), iterable):
1591+
pass
1592+
return
1593+
try:
1594+
for i, element in enumerate(iterable):
1595+
if i == nexti:
1596+
yield element
1597+
nexti = next(it)
1598+
except StopIteration:
1599+
# Consume to *stop*.
1600+
for i, element in zip(range(i + 1, stop), iterable):
1601+
pass
1602+
1603+
def test_islice_recipe(self):
1604+
self.assertEqual(list(self.islice('ABCDEFG', 2)), list('AB'))
1605+
self.assertEqual(list(self.islice('ABCDEFG', 2, 4)), list('CD'))
1606+
self.assertEqual(list(self.islice('ABCDEFG', 2, None)), list('CDEFG'))
1607+
self.assertEqual(list(self.islice('ABCDEFG', 0, None, 2)), list('ACEG'))
1608+
# Test items consumed.
1609+
it = iter(range(10))
1610+
self.assertEqual(list(self.islice(it, 3)), list(range(3)))
1611+
self.assertEqual(list(it), list(range(3, 10)))
1612+
it = iter(range(10))
1613+
self.assertEqual(list(self.islice(it, 3, 3)), [])
1614+
self.assertEqual(list(it), list(range(3, 10)))
1615+
# Test that slice finishes in predictable state.
1616+
c = count()
1617+
self.assertEqual(list(self.islice(c, 1, 3, 50)), [1])
1618+
self.assertEqual(next(c), 3)
1619+
1620+
15741621
class TestGC(unittest.TestCase):
15751622

15761623
def makecycle(self, iterator, container):
@@ -2125,6 +2172,17 @@ def test_permutations_sizeof(self):
21252172
... "Return function(0), function(1), ..."
21262173
... return map(function, count(start))
21272174
2175+
>>> import collections
2176+
>>> def consume(iterator, n=None):
2177+
... "Advance the iterator n-steps ahead. If n is None, consume entirely."
2178+
... # Use functions that consume iterators at C speed.
2179+
... if n is None:
2180+
... # feed the entire iterator into a zero-length deque
2181+
... collections.deque(iterator, maxlen=0)
2182+
... else:
2183+
... # advance to the empty slice starting at position n
2184+
... next(islice(iterator, n, n), None)
2185+
21282186
>>> def nth(iterable, n, default=None):
21292187
... "Returns the nth item or a default value"
21302188
... return next(islice(iterable, n, None), default)
@@ -2265,6 +2323,14 @@ def test_permutations_sizeof(self):
22652323
>>> list(islice(tabulate(lambda x: 2*x), 4))
22662324
[0, 2, 4, 6]
22672325
2326+
>>> it = iter(range(10))
2327+
>>> consume(it, 3)
2328+
>>> next(it)
2329+
3
2330+
>>> consume(it)
2331+
>>> next(it, 'Done')
2332+
'Done'
2333+
22682334
>>> nth('abcde', 3)
22692335
'd'
22702336
@@ -2353,6 +2419,7 @@ def test_main(verbose=None):
23532419
test_classes = (TestBasicOps, TestVariousIteratorArgs, TestGC,
23542420
RegressionTests, LengthTransparency,
23552421
SubclassWithKwargsTest, TestExamples,
2422+
TestPurePythonRoughEquivalents,
23562423
SizeofTest)
23572424
support.run_unittest(*test_classes)
23582425

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)
0