Sending StopIteration to for loop from outside of the iterator

冷暖自知 提交于 2019-12-05 23:33:31

问题


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!


回答1:


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



回答2:


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



回答3:


I think it's because StopIteration wasn't sent from inside of iterator it. (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



回答4:


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


来源:https://stackoverflow.com/questions/6920206/sending-stopiteration-to-for-loop-from-outside-of-the-iterator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!