Sending StopIteration to for loop from outside of the iterator
There are several ways to break out of a few nested loops
They are:
1) to use break-continue
for x in xrange(10):
for y in xrange(10):
print x*y
if x*y > 50:
break
else:
continue # only executed if break was not used
break
2) to use return
def foo():
for x in range(10):
for y in range(10):
print x*y
if x*y > 50:
return
foo()
3) to use special exception
class BreakIt(Exception): pass
try:
for x in range(10):
for y in range(10):
print x*y
if x*y > 50:
raise BreakIt
except BreakIt:
pass
I had some thought that there could be some other way to do it. It is by using StopIteration exception sent directly to the outer loop. I wrote this code
it = iter(range(10))
for i in it:
for j in range(10):
if i*j == 20:
raise StopIteration
Unfortunately, StopIteration hadn't been caught by any for-loop and that code produced an ugly Traceback. I think it's because StopIteration wasn't sent from inside of iterator it. (that's my guess, I'm not sure about it).
Is there any way that I can send 开发者_Go百科StopIteration to the outer loop?
Thanks!
You can do something like this with coroutines:
def stoppable_iter(iterable):
it = iter(iterable)
for v in it:
x = yield v
if x:
yield
return
And then use it like this:
it = stoppable_iter(range(10))
for i in it:
for j in range(10):
print i, j
if i*j == 20:
it.send(StopIteration) # or any value that evaluates as True
break
And a brief example of how it works:
>>> t = stoppable_iter(range(10))
>>> t.next()
0
>>> t.next()
1
>>> t.send(StopIteration)
>>> t.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Another approach to nested loops you wish to break from, is to collapse them. So something like
for x, y in ((x, y) for x in range(10) for y in range(10)):
print x*y
if x*y > 50: break
I think it's because
StopIteration
wasn't sent from inside of iteratorit
. (that's my guess, I'm not sure about it).
Exactly right.
Is there any way that I can send
StopIteration
to the other loop?
Same way as your #3, except using StopIteration
instead of an exception you define. It's a good one to use anyway.
In the comments I mentioned writing an iterator that can be told to raise StopIteration the next time through the loop. Here's the sort of thing I'm talking about:
class StoppableIterator(object):
def __init__(self, iterable):
self._iter = iter(iterable)
self._stop = False
def __iter__(self):
return self
def stop(self):
self._stop = True
def next(self):
if self._stop:
raise StopIteration
return next(self._iter)
Usage:
si = StoppableIterator([2, 3, 5, 7, 11, 13])
for i in si:
for j in xrange(i):
print i, j
if j == 7:
si.stop() # will break out of outer loop next iteration
break # breaks out of inner loop
You can use .close
, which every generator have since Python 2.5:
Code is in Python 3.2, but it should work in 2.x as well.
In Python 2.x I'd use xrange
instead of range
.
outer_loop_iterator = (i for i in range(10)) #we need named generator
for x in outer_loop_iterator:
for y in range(10):
print(x*y)
if x*y > 50:
outer_loop_iterator.close()
break #I'm affraid that without this inner loop could still work
精彩评论