【问题标题】:Sending StopIteration to for loop from outside of the iterator从迭代器外部将 StopIteration 发送到 for 循环
【发布时间】:2011-10-18 17:04:18
【问题描述】:

有几种方法可以打破几个嵌套循环

他们是:

1) 使用中断继续

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) 使用返回

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 循环捕获,并且该代码产生了丑陋的 Traceback。 我认为这是因为 StopIteration 不是从迭代器 it 内部发送的。 (这是我的猜测,我不确定)。

有什么方法可以将 StopIteration 发送到外循环?

谢谢!

【问题讨论】:

    标签: python loops for-loop iterator stopiteration


    【解决方案1】:

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

    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
    

    【讨论】:

    • 拜托,你能解释一下,soppable_iter 是如何工作的吗?我不明白以 if x: ... 开头的部分
    • 这真的很聪明,比我提供的课程版本要短得多!
    • 我认为我需要仔细研究协程,因为以我目前的知识,我无法掌握 stoppable_iter 中的算法。
    • 如果我们有 3 个嵌套循环,我们需要这样写: it1.send(StopIteration) it2.send(StipIteration) break 对吗?
    • 如果在stoppable_iter 返回的生成器上调用next,则x 将返回Nonev。如果调用send,则x 将是发送的值,这将导致生成器完成,只要发送非假值。
    【解决方案2】:

    另一种要打破嵌套循环的方法是折叠它们。所以像

    for x, y in ((x, y) for x in range(10) for y in range(10)):
        print x*y
        if x*y > 50: break
    

    【讨论】:

    • 是的。甚至可以这样写: from itertools import product >>> for i, j, k in product(range(5), range(6), range(7)): ... pass 我只是感兴趣,如果有什么方法可以捕获 alter StopIteration 以便它可以被特定的 for 循环捕获。
    【解决方案3】:

    我认为这是因为 StopIteration 不是从迭代器 it 内部发送的。 (这是我的猜测,我不确定)。

    完全正确。

    有什么方法可以将StopIteration 发送到另一个循环?

    与您的 #3 相同,除了使用 StopIteration 而不是您定义的异常。无论如何,它是一个很好的使用。

    在 cmets 中,我提到编写一个迭代器,可以告诉它在下一次循环中引发 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
    

    【讨论】:

    • 也许我们可以通过某种方式修改 StopIteration 以使其被必要的 for 循环捕获?我认为这可能是可能的,因为如果迭代器引发 StopIteration,for-loop except 部分必须确定 StopIteration 的来源并传播它,如果不是为了它,而是为了一些上层循环。因此,通过编辑 StopIteration 对象的一些参数,我们可以使其被特定的 for 循环捕获。
    • 不,它不必“确定它来自哪里”——它只是在next() 调用周围放置一个try/except,它从迭代器中获取下一个值。如果捕获到异常,它就知道它来自迭代器。不过,您可以想象编写一个迭代器来引发 StopException 在调用特殊方法后的下一次调用。不过,它只能在顶部跳出循环。最好把它举到你需要的地方,然后自己接住。
    • 哦。现在我看到了。所以当我们写东西时: for i in iterable: #do_sth for-loop 装饰了迭代器的 next() 方法并捕获了 StopIteration。我不明白如何编写一个迭代器来在调用特殊方法后下一次调用它时引发 StopException,只是没有设法得到这个想法。
    • 对不起,我不能投票给你发帖(没有足够的代表)。你的帖子真的很有帮助!
    • 我会将stop 方法重命名为close,因为generators have .close method
    【解决方案4】:

    您可以使用.close,自 Python 2.5 以来每个生成器都有:

    代码在 Python 3.2 中,但它也应该在 2.x 中工作。
    在 Python 2.x 中,我会使用 xrange 而不是 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-03
      • 1970-01-01
      • 2015-02-08
      • 2016-06-20
      • 1970-01-01
      相关资源
      最近更新 更多