从迭代器外部向for循环发送StopIteration

8 投票
4 回答
4975 浏览
提问于 2025-04-16 22:46

有几种方法可以跳出嵌套的循环

它们是:

1) 使用 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) 使用 return

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

3) 使用特殊的异常

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

我曾想过可能还有其他方法可以做到这一点。 那就是直接向外层循环发送 StopIteration 异常。 我写了这段代码

it = iter(range(10))
for i in it:
    for j in range(10):
        if i*j == 20:
            raise StopIteration

不幸的是,StopIteration 没有被任何 for 循环捕获,这段代码产生了一个难看的错误追踪。 我觉得这可能是因为 StopIteration 不是从迭代器 it 内部发送的。(这是我的猜测,我不太确定)。

有没有办法可以把 StopIteration 发送到外层循环呢?

谢谢!

4 个回答

1

我觉得这是因为 StopIteration 这个信号没有从迭代器 it 内部发送出来。(这是我的猜测,我不太确定。)

说得对。

有没有办法可以把 StopIteration 发送到另一个循环里呢?

可以用和你提到的第3种方法一样的方式,只不过这次用 StopIteration 替代你自己定义的异常。其实用这个信号也挺好的。

在评论中,我提到过写一个迭代器,可以在下次循环时被指示抛出 StopIteration。我说的就是这种情况:

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)

用法:

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

另一种处理你想要跳出嵌套循环的方法是将它们合并。也就是说,可以像下面这样做:

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

你可以用协程来做一些这样的事情:

def stoppable_iter(iterable):
    it = iter(iterable)
    for v in it:
        x = yield v
        if x:
            yield
            return

然后可以这样使用它:

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

下面是一个简单的例子,展示它是怎么工作的:

>>> 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

撰写回答