Enviando StopIteration a for loop desde fuera del iterador

Hay varias formas de romper algunos bucles nesteds

Son:

1) usar 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) utilizar retorno

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

3) usar la excepción especial

 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 

Pensé que podría haber otra manera de hacerlo. Es mediante el uso de la excepción StopIteration enviada directamente al bucle externo. Yo escribi este codigo

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

Desafortunadamente, StopIteration no había sido capturado por ningún bucle for y ese código produjo un Traceback feo. Creo que es porque StopIteration no se envió desde dentro del iterador. (Esa es mi suposición, no estoy seguro de eso).

¿Hay alguna manera de que pueda enviar StopIteration al bucle externo?

¡Gracias!

Puedes hacer algo así con las coroutinas:

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

Y luego úsalo así:

 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 

Y un breve ejemplo de cómo funciona:

 >>> t = stoppable_iter(range(10)) >>> t.next() 0 >>> t.next() 1 >>> t.send(StopIteration) >>> t.next() Traceback (most recent call last): File "", line 1, in  StopIteration 

Otro enfoque de los bucles nesteds de los que desea romper, es colapsarlos. Así que algo como

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

Creo que es porque StopIteration no se envió desde dentro del iterador. (Esa es mi suposición, no estoy seguro de eso).

Exactamente correcto.

¿Hay alguna manera de que pueda enviar StopIteration al otro bucle?

De la misma forma que su # 3, excepto que usa StopIteration lugar de una excepción que usted define. Es un buen uso de todos modos.

En los comentarios que mencioné, se escribió un iterador que se puede indicar para que genere StopIteration la próxima vez a través del bucle. Aquí está el tipo de cosas de las que estoy hablando:

 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) 

Uso:

 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 

Puedes usar .close , que todos los generadores tienen desde Python 2.5:

El código está en Python 3.2, pero también debería funcionar en 2.x.
En Python 2.x usaría xrange lugar de 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