2011-08-03 53 views
7

有幾種方法,打破了幾個嵌套循環發送的StopIteration爲循環從迭代器之外

它們是:

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循環和代碼生成一個醜陋的回溯。 我認爲這是因爲StopIteration不是從迭代器內發送的。 (這是我的猜測,我不確定)。

有什麼方法可以將StopIteration發送到外部循環?

謝謝!

回答

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

請問,你能解釋一下,soppable_iter的工作原理嗎?我不明白以if開頭的部分:... – ovgolovin

+0

這真的很聰明,比我提供的課程版本短得多! – kindall

+0

我想我需要仔細看看協程,因爲用我目前的知識,我無法掌握stoppable_iter中的算法。 – ovgolovin

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

也許有一些方法可以修補StopIteration,使其被必需的for-loop捕獲?我認爲這可能是因爲如果迭代器引發StopIteration,除了section之外的for循環必須從StopIteration的來源確定並傳播它,如果它不是它,但是對於一些上層循環。所以,通過編輯StopIteration對象的一些參數,我們可以使它被特定的for循環捕獲。 – ovgolovin

+0

不,它不必「確定它來自哪裏」 - 它只是在'next()'調用周圍放置一個'try/except',它從迭代器獲取下一個值。如果捕獲異常,它知道它來自迭代器。不過,可以想象在下一次調用特殊方法之後調用'StopException'來編寫迭代器。儘管如此,它只能擺脫頂層的循環。更好地提高它在任何你需要的地方,並抓住它自己。 – kindall

+0

哦。現在我明白了。所以當我們寫一些東西時:爲了我在迭代中:#do_sth for-loop裝飾迭代器的next()方法並捕獲StopIteration。我不知道如何編寫一個迭代器來在下一次調用特殊方法之後調用StopException時引發StopException,只是沒有設法獲得這個想法。 – ovgolovin

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

是的。它甚至可以這樣寫:從itertools進口產品 >>>對於產品中的i,j,k(範圍(5),範圍(6),範圍(7)): ...通過我只是感興趣的是,如果有任何方法我可以捕捉改變StopIteration,以便它可以被特定的for-loop捕獲。 – ovgolovin

1

您可以使用.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