Sending StopIteration for a loop from outside the iterator

There are several ways to break out of several nested loops.

It:

1) 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) use return

 def foo(): for x in range(10): for y in range(10): print x*y if x*y > 50: return foo() 

3) use a 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 thought there might be another way to do this. It is through the exception that StopIteration is sent directly to the external 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 was not caught by any for loop, and this code caused an ugly Traceback. I think because StopIteration was not sent from inside iterator it . (what I think, I'm not sure about that).

Is there a way I can send StopIteration to an outer loop?

Thanks!

+7
source share
4 answers

You can do something similar 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 this 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 
+4
source

Another approach to the nested loops you want to split is to smooth 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 
+4
source

I think because StopIteration not sent from inside the iterator. (what I think, I'm not sure about that).

Correctly.

Is there a way I can send StopIteration to another loop?

Same as your # 3, except for using StopIteration instead of the exception you selected. In any case, this is a good option.

In the comments, I mentioned writing an iterator that could be told that next time StopIteration will be called through the loop. This is what I am 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) 

Using:

 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 
+1
source

You can use the .close that every generator has with Python 2.5:

The code is in Python 3.2, but it should work in 2.x.
In Python 2.x, I would 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 
+1
source

All Articles