anon582847382
anon582847382

Reputation: 20361

Why isn't a StopIteration raised here?

Take a look at this, the crux of the question is at the bottom:

>>> scan = iter('FHUR203459')
>>> while True:
        print(next(scan))


F
H
U
R
2
0
3
4
5
9
Traceback (most recent call last):
  File "<pyshell#11>", line 2, in <module>
    print(next(scan))
StopIteration
>>> scan = iter('FHUR203459')
>>> for i in range(12):  # 12 * 2 for each join is 24, much longer than the string; should raise error.
        print(''.join(next(scan) for i in range(2)))


FH
UR
20
34
59







>>>

In other words, we can see that the iterator reaches its end in both situations, however it only raises a StopIteration in the first, even though next() is used in both situations after it reached the end. Why does using it in join seem to evade the error? Or is this a bug?

Upvotes: 2

Views: 572

Answers (2)

thefourtheye
thefourtheye

Reputation: 239453

In the first case, the StopIteration is not handled anywhere. But in the second case,

''.join(next(scan) for i in range(2))

we pass a generator expression to the ''.join, which handles the StopIteration raised by next(scan) and exits every time. That is why ''.join produces empty strings.

You can slightly modify the same, and pass a list to ''.join and see the exception being raised, yourself, like this

>>> scan = iter('FHUR203459')
>>> for i in range(12):
...     print(''.join([next(scan) for i in range(2)]))
... 
FH
UR
20
34
59
Traceback (most recent call last):
  File "<input>", line 2, in <module>
  File "<input>", line 2, in <listcomp>
StopIteration

It shows that the StopIteration is actually raised, and List Comprehension takes the hit, this time.

Upvotes: 2

Martijn Pieters
Martijn Pieters

Reputation: 1121654

str.join() calls list() on the generator, and that call swallows the StopIteration.

Anything that consumes an iterator must catch StopIteration; it doesn't matter then exactly what raised the exception; the generator expression or anything used by the generator expression:

>>> def raises_stopiteration(): raise StopIteration
... 
>>> next(raises_stopiteration() for _ in range(10))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
  File "<stdin>", line 1, in raises_stopiteration
StopIteration
>>> list(raises_stopiteration() for _ in range(10))
[]

Upvotes: 2

Related Questions