Skip to content

Commit 84efcec

Browse files
authored
[3.13] Small improvements to the itertools docs (GH-123885) (#125075)
1 parent 6925e5b commit 84efcec

File tree

2 files changed

+176
-3
lines changed

2 files changed

+176
-3
lines changed

Doc/library/itertools.rst

+15-1
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ loops that truncate the stream.
474474
If *start* is zero or ``None``, iteration starts at zero. Otherwise,
475475
elements from the iterable are skipped until *start* is reached.
476476

477-
If *stop* is ``None``, iteration continues until the iterator is
477+
If *stop* is ``None``, iteration continues until the iterable is
478478
exhausted, if at all. Otherwise, it stops at the specified position.
479479

480480
If *step* is ``None``, the step defaults to one. Elements are returned
@@ -503,6 +503,10 @@ loops that truncate the stream.
503503
yield element
504504
next_i += step
505505

506+
If the input is an iterator, then fully consuming the *islice*
507+
advances the input iterator by ``max(start, stop)`` steps regardless
508+
of the *step* value.
509+
506510

507511
.. function:: pairwise(iterable)
508512

@@ -601,6 +605,8 @@ loops that truncate the stream.
601605
# product('ABCD', 'xy') → Ax Ay Bx By Cx Cy Dx Dy
602606
# product(range(2), repeat=3) → 000 001 010 011 100 101 110 111
603607

608+
if repeat < 0:
609+
raise ValueError('repeat argument cannot be negative')
604610
pools = [tuple(pool) for pool in iterables] * repeat
605611

606612
result = [[]]
@@ -684,6 +690,8 @@ loops that truncate the stream.
684690
Roughly equivalent to::
685691

686692
def tee(iterable, n=2):
693+
if n < 0:
694+
raise ValueError('n must be >= 0')
687695
iterator = iter(iterable)
688696
shared_link = [None, None]
689697
return tuple(_tee(iterator, shared_link) for _ in range(n))
@@ -703,6 +711,12 @@ loops that truncate the stream.
703711
used anywhere else; otherwise, the *iterable* could get advanced without
704712
the tee objects being informed.
705713

714+
When the input *iterable* is already a tee iterator object, all
715+
members of the return tuple are constructed as if they had been
716+
produced by the upstream :func:`tee` call. This "flattening step"
717+
allows nested :func:`tee` calls to share the same underlying data
718+
chain and to have a single update step rather than a chain of calls.
719+
706720
``tee`` iterators are not threadsafe. A :exc:`RuntimeError` may be
707721
raised when simultaneously using iterators returned by the same :func:`tee`
708722
call, even if the original *iterable* is threadsafe.

Lib/test/test_itertools.py

+161-2
Original file line numberDiff line numberDiff line change
@@ -1288,12 +1288,16 @@ def product1(*args, **kwds):
12881288
else:
12891289
return
12901290

1291-
def product2(*args, **kwds):
1291+
def product2(*iterables, repeat=1):
12921292
'Pure python version used in docs'
1293-
pools = list(map(tuple, args)) * kwds.get('repeat', 1)
1293+
if repeat < 0:
1294+
raise ValueError('repeat argument cannot be negative')
1295+
pools = [tuple(pool) for pool in iterables] * repeat
1296+
12941297
result = [[]]
12951298
for pool in pools:
12961299
result = [x+[y] for x in result for y in pool]
1300+
12971301
for prod in result:
12981302
yield tuple(prod)
12991303

@@ -2062,6 +2066,161 @@ def test_islice_recipe(self):
20622066
self.assertEqual(next(c), 3)
20632067

20642068

2069+
def test_tee_recipe(self):
2070+
2071+
# Begin tee() recipe ###########################################
2072+
2073+
def tee(iterable, n=2):
2074+
if n < 0:
2075+
raise ValueError('n must be >= 0')
2076+
iterator = iter(iterable)
2077+
shared_link = [None, None]
2078+
return tuple(_tee(iterator, shared_link) for _ in range(n))
2079+
2080+
def _tee(iterator, link):
2081+
try:
2082+
while True:
2083+
if link[1] is None:
2084+
link[0] = next(iterator)
2085+
link[1] = [None, None]
2086+
value, link = link
2087+
yield value
2088+
except StopIteration:
2089+
return
2090+
2091+
# End tee() recipe #############################################
2092+
2093+
n = 200
2094+
2095+
a, b = tee([]) # test empty iterator
2096+
self.assertEqual(list(a), [])
2097+
self.assertEqual(list(b), [])
2098+
2099+
a, b = tee(irange(n)) # test 100% interleaved
2100+
self.assertEqual(lzip(a,b), lzip(range(n), range(n)))
2101+
2102+
a, b = tee(irange(n)) # test 0% interleaved
2103+
self.assertEqual(list(a), list(range(n)))
2104+
self.assertEqual(list(b), list(range(n)))
2105+
2106+
a, b = tee(irange(n)) # test dealloc of leading iterator
2107+
for i in range(100):
2108+
self.assertEqual(next(a), i)
2109+
del a
2110+
self.assertEqual(list(b), list(range(n)))
2111+
2112+
a, b = tee(irange(n)) # test dealloc of trailing iterator
2113+
for i in range(100):
2114+
self.assertEqual(next(a), i)
2115+
del b
2116+
self.assertEqual(list(a), list(range(100, n)))
2117+
2118+
for j in range(5): # test randomly interleaved
2119+
order = [0]*n + [1]*n
2120+
random.shuffle(order)
2121+
lists = ([], [])
2122+
its = tee(irange(n))
2123+
for i in order:
2124+
value = next(its[i])
2125+
lists[i].append(value)
2126+
self.assertEqual(lists[0], list(range(n)))
2127+
self.assertEqual(lists[1], list(range(n)))
2128+
2129+
# test argument format checking
2130+
self.assertRaises(TypeError, tee)
2131+
self.assertRaises(TypeError, tee, 3)
2132+
self.assertRaises(TypeError, tee, [1,2], 'x')
2133+
self.assertRaises(TypeError, tee, [1,2], 3, 'x')
2134+
2135+
# Tests not applicable to the tee() recipe
2136+
if False:
2137+
# tee object should be instantiable
2138+
a, b = tee('abc')
2139+
c = type(a)('def')
2140+
self.assertEqual(list(c), list('def'))
2141+
2142+
# test long-lagged and multi-way split
2143+
a, b, c = tee(range(2000), 3)
2144+
for i in range(100):
2145+
self.assertEqual(next(a), i)
2146+
self.assertEqual(list(b), list(range(2000)))
2147+
self.assertEqual([next(c), next(c)], list(range(2)))
2148+
self.assertEqual(list(a), list(range(100,2000)))
2149+
self.assertEqual(list(c), list(range(2,2000)))
2150+
2151+
# test invalid values of n
2152+
self.assertRaises(TypeError, tee, 'abc', 'invalid')
2153+
self.assertRaises(ValueError, tee, [], -1)
2154+
2155+
for n in range(5):
2156+
result = tee('abc', n)
2157+
self.assertEqual(type(result), tuple)
2158+
self.assertEqual(len(result), n)
2159+
self.assertEqual([list(x) for x in result], [list('abc')]*n)
2160+
2161+
2162+
# Tests not applicable to the tee() recipe
2163+
if False:
2164+
# tee pass-through to copyable iterator
2165+
a, b = tee('abc')
2166+
c, d = tee(a)
2167+
self.assertTrue(a is c)
2168+
2169+
# test tee_new
2170+
t1, t2 = tee('abc')
2171+
tnew = type(t1)
2172+
self.assertRaises(TypeError, tnew)
2173+
self.assertRaises(TypeError, tnew, 10)
2174+
t3 = tnew(t1)
2175+
self.assertTrue(list(t1) == list(t2) == list(t3) == list('abc'))
2176+
2177+
# test that tee objects are weak referencable
2178+
a, b = tee(range(10))
2179+
p = weakref.proxy(a)
2180+
self.assertEqual(getattr(p, '__class__'), type(b))
2181+
del a
2182+
gc.collect() # For PyPy or other GCs.
2183+
self.assertRaises(ReferenceError, getattr, p, '__class__')
2184+
2185+
ans = list('abc')
2186+
long_ans = list(range(10000))
2187+
2188+
# Tests not applicable to the tee() recipe
2189+
if False:
2190+
# check copy
2191+
a, b = tee('abc')
2192+
self.assertEqual(list(copy.copy(a)), ans)
2193+
self.assertEqual(list(copy.copy(b)), ans)
2194+
a, b = tee(list(range(10000)))
2195+
self.assertEqual(list(copy.copy(a)), long_ans)
2196+
self.assertEqual(list(copy.copy(b)), long_ans)
2197+
2198+
# check partially consumed copy
2199+
a, b = tee('abc')
2200+
take(2, a)
2201+
take(1, b)
2202+
self.assertEqual(list(copy.copy(a)), ans[2:])
2203+
self.assertEqual(list(copy.copy(b)), ans[1:])
2204+
self.assertEqual(list(a), ans[2:])
2205+
self.assertEqual(list(b), ans[1:])
2206+
a, b = tee(range(10000))
2207+
take(100, a)
2208+
take(60, b)
2209+
self.assertEqual(list(copy.copy(a)), long_ans[100:])
2210+
self.assertEqual(list(copy.copy(b)), long_ans[60:])
2211+
self.assertEqual(list(a), long_ans[100:])
2212+
self.assertEqual(list(b), long_ans[60:])
2213+
2214+
# Issue 13454: Crash when deleting backward iterator from tee()
2215+
forward, backward = tee(repeat(None, 2000)) # 20000000
2216+
try:
2217+
any(forward) # exhaust the iterator
2218+
del backward
2219+
except:
2220+
del forward, backward
2221+
raise
2222+
2223+
20652224
class TestGC(unittest.TestCase):
20662225

20672226
def makecycle(self, iterator, container):

0 commit comments

Comments
 (0)