Reputation: 83
I have noticed that Recipes section in the itertools
documentation has some useful code, like consume
, but I have found a problem.
from itertools import islice
numbers = iter(range(10))
for i in numbers:
print i
next(islice(numbers, 3, 3), None)
the code above works fine, it prints [0,4,8]
, the next(islice(numbers, 3, 3), None)
trick is taken from the consume
recipe.
We can see islice
here is playing an important role. But, when I replace itertools.islice
with the code provided in python documentation for that function, the code is not working as I expected:
#from itertools import islice
def islice(iterable, *args):
# islice('ABCDEFG', 2) --> A B
# islice('ABCDEFG', 2, 4) --> C D
# 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 200, s.step or 1))
nexti = next(it)
for i, element in enumerate(iterable):
if i == nexti:
yield element
nexti = next(it)
numbers = iter(range(10))
for i in numbers:
print i
next(islice(numbers, 3, 3), None)
the output is different from the output above; all numbers in the range are printed and the islice()
consume line doesn't appear to do anything.
Can anybody can explain why this is and the way islice
works here?
Upvotes: 4
Views: 3262
Reputation: 1122232
The slice start and stop arguments are equal, so the next(it)
call on the xrange raises StopIteration
:
>>> s = slice(3, 3, None)
>>> it = iter(xrange(s.start or 0, s.stop or 200, s.step or 1))
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
and the slice()
generator function immediately terminates. This is only logical, the xrange()
function is not going to produce any numbers in that case.
The itertools
documentation is illustrative, and not meant to be complete. You've found one edgecase where the sample Python code does not work the same way that the C implementation works.
To make the Python code work for this case as well, catch the StopIteration
exception, and test for the edgecase explicitly in the loop:
def islice(iterable, *args):
s = slice(*args)
it = iter(xrange(s.start or 0, s.stop or 200, s.step or 1))
try:
nexti = next(it)
consume = False
except StopIteration:
if s.start < 1:
return
nexti = s.start - 1
consume = True
for i, element in enumerate(iterable):
if i == nexti:
if consume:
return
yield element
nexti = next(it)
The Python sample code uses a slice()
object to make the code more compact and easier to read; the C code does its own start, stop and step parsing, and handles the count itself instead of delegating to xrange()
, and thus doesn't run into this edgecase.
Upvotes: 4