Search code examples
pythonloopsfor-loopiteratorstopiteration

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 StopIteration to the outer loop?

Thanks!


Solution

  • 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