Отправка StopIteration в цикл for снаружи итератора - PullRequest
7 голосов
/ 03 августа 2011

Есть несколько способов вырваться из нескольких вложенных циклов

Это:

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) использоватьвернуть

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

К сожалению, StopIорт не был перехвачен циклом for, и этот код вызвал ужасную трассировку.Я думаю, это потому, что StopItered не был отправлен изнутри итератора it .(это мое предположение, я не уверен в этом).

Есть ли способ, которым я могу отправить StopIteration во внешний цикл?

Спасибо!

Ответы [ 4 ]

4 голосов
/ 03 августа 2011

Другой подход к вложенным циклам, от которых вы хотите разорвать, - это свернуть их.Так что-то вроде

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 голосов
/ 03 августа 2011

Вы можете сделать что-то подобное с сопрограммами:

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
1 голос
/ 23 ноября 2014

Вы можете использовать .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
1 голос
/ 03 августа 2011

Я думаю, это потому, что StopIteration не был отправлен изнутри итератора it. (это мое предположение, я не уверен в этом).

Совершенно верно.

Есть ли способ, которым я могу отправить StopIteration в другой цикл?

Так же, как ваш # 3, за исключением того, что вы используете StopIteration вместо определенного вами исключения. В любом случае, это хороший вариант.

В комментариях я упомянул написание итератора, которому можно было бы вызвать вызов StopItered в следующий раз в цикле. Вот о чем я говорю:

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