2011-08-03 19 views
7

Istnieje kilka sposobów, aby wydostać się z kilku zagnieżdżonych pętliWysyłanie StopIteration do pętli spoza iterator

one:

1) używać włamanie kontynuować

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) do stosowania powrót

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

3) użycie specjalnego wyjątku

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 

Myślałem, że może być jakiś inny sposób na zrobienie tego. Używa wyjątku StopIteration wysyłanego bezpośrednio do zewnętrznej pętli. Napisałem ten kod

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

Niestety StopIteration nie zostały zaczepione przez pętli for a kod produkowane brzydki Traceback. Myślę, że to dlatego, że StopIteracja nie została wysłana z wnętrza iteratora it. (to jest moje przypuszczenie, nie jestem tego pewien).

Czy istnieje sposób, w jaki mogę wysłać StopIteration do zewnętrznej pętli?

Dzięki!

Odpowiedz

4

można zrobić coś takiego z współprogram:

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

a następnie używać go tak:

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 

i krótki przykład jak to działa:

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

Proszę, czy możesz wyjaśnić, jak działa soppable_iter? Nie rozumiem tej części zaczynającej się od x: ... – ovgolovin

+0

To jest naprawdę sprytne i znacznie krótsze niż wersja klasowa, którą dostarczyłem! – kindall

+0

Myślę, że muszę bliżej przyjrzeć się coroutines, ponieważ z moją obecną wiedzą o tym nie mogę pojąć, co robi algorytm w stoppable_iter. – ovgolovin

1

myślę, że to dlatego StopIteration nie został wysłany z wewnątrz iteracyjnej it. (to jest moje przypuszczenie, nie jestem tego pewien).

Dokładnie w prawo.

Czy jest jakiś sposób, żebym mógł wysłać StopIteration do drugiej pętli?

Tak samo jak twoje # 3, z wyjątkiem używania StopIteration zamiast zdefiniowanego wyjątku. I tak warto go używać.

W komentarzach, o których wspomniałem, napisałem iterator, który może zostać poinformowany o podniesieniu StopIteration następnym razem przez pętlę. Oto coś takiego mi chodzi:

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) 

Wykorzystanie:

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

Być może jest jakiś sposób, abyśmy mogli majstrować przy StopIteracji, aby mógł zostać przechwycony przez niezbędne pętle? Myślę, że może to być możliwe, ponieważ jeśli iterator podnosi StopIteration, pętla For-loop oprócz sekcji musi określić skąd pochodzi ta StopIteracja i ją propagować, jeśli nie jest ona dla niego, ale dla niektórych górnych pętli. Tak więc, edytując niektóre parametry obiektu StopIteration, możemy go złapać przez specyficzną pętlę for. – ovgolovin

+0

Nie, nie musi "określać, skąd pochodzi" - po prostu wstawia 'try/except' wokół wywołania' next() ', które pobiera następną wartość z iteratora. Jeśli przechwyci wyjątek, wie, że pochodzi on z iteratora. Można sobie wyobrazić napisanie iteratora w celu podniesienia 'StopException' przy następnym wywołaniu po wywołaniu specjalnej metody. Mogłoby jednak wyjść z pętli tylko na górze. Lepiej go podnieść wszędzie, gdzie chcesz i złap go samemu. – kindall

+0

Oh. Teraz to widzę. Kiedy więc coś piszemy: dla it in iterable: #do_sth for-loop dekoruje metodę next() iteratora i przechwytuje StopIteration. Nie podaję się i jak mogę napisać iterator do podniesienia StopException następnym razem, gdy zostanie wywołany po wywołaniu specjalnej metody, po prostu nie udało się uzyskać tego pomysłu. – ovgolovin

4

Innym podejściem do zagnieżdżonych pętli, które chcesz zerwać z, jest ich upadku.Więc coś

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

Tak. Można nawet napisać w ten sposób: od itertools import produktu >>> dla i, j, k w produkcie (zakres (5), zakres (6), zakres (7)): ... pass I'm just ciekawi mnie, czy istnieje jakikolwiek sposób, aby uchwycić zmianę StopIteracji, aby mogła zostać przechwycona przez konkretną pętlę for. – ovgolovin

1

Możesz użyć .close, które każdy generator ma od Python 2.5:

Kod jest w Pythonie 3.2, ale powinien działać w 2.x, jak również.
W Pythonie 2.x użyłbym xrange zamiast 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