Как вырваться из нескольких циклов в Python?

Учитывая следующий код (это не работает):

while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": break 2 #this doesn't work :(
        if ok == "n" or ok == "N": break
    #do more processing with menus and stuff

Есть ли способ сделать эту работу? Или мне нужно сделать одну проверку, чтобы выйти из цикла ввода, затем другую, более ограниченную, проверку во внешнем цикле, чтобы разорвать все вместе, если пользователь удовлетворен?

Edit-FYI: get_input - это короткая функция, которую я написал, которая поддерживает отображение подсказки и значений по умолчанию, а также всех этих причуд и возвратов stdin.readline().strip()

39 ответов

Решение

Моим первым инстинктом было бы преобразовать вложенный цикл в функцию и использовать return вырваться.

Вот еще один короткий подход. Недостатком является то, что вы можете разорвать только внешний цикл, но иногда это именно то, что вы хотите.

for a in xrange(10):
    for b in xrange(20):
        if something(a, b):
            # Break the inner loop...
            break
    else:
        # Continue if the inner loop wasn't broken.
        continue
    # Inner loop was broken, break the outer.
    break

Здесь используется конструкция for / else, объясненная в: Почему Python использует "else" после циклов for и while?

Основная идея: только кажется, что внешний цикл всегда прерывается. Но если внутренний цикл не прерывается, внешний цикл тоже не будет.

continue утверждение это магия здесь. Это в пункте для другого. По определению это происходит, если нет внутреннего разрыва. В этой ситуации continue аккуратно обходит внешний разрыв.

PEP 3136 предлагает помеченный перерыв / продолжение. Гвидо отверг это, потому что "код, настолько сложный, чтобы требовать эту функцию, очень редок". Однако в PEP упоминаются некоторые обходные пути (например, метод исключения), в то время как Гвидо считает, что рефакторинг для использования return будет проще в большинстве случаев.

Во-первых, обычная логика полезна.

Если по какой-либо причине условия прекращения не могут быть разработаны, исключения являются запасным планом.

class GetOutOfLoop( Exception ):
    pass

try:
    done= False
    while not done:
        isok= False
        while not (done or isok):
            ok = get_input("Is this ok? (y/n)")
            if ok in ("y", "Y") or ok in ("n", "N") : 
                done= True # probably better
                raise GetOutOfLoop
        # other stuff
except GetOutOfLoop:
    pass

Для этого конкретного примера исключение может не потребоваться.

С другой стороны, у нас часто есть опции "Y", "N" и "Q" в приложениях символьного режима. Для опции "Q" мы хотим немедленный выход. Это более исключительно.

Введите новую переменную, которую вы будете использовать как "прерыватель цикла". Сначала присвойте ему что-нибудь (False,0 и т. Д.), А затем, во внешнем цикле, перед тем как выйти из него, измените значение на что-то другое (True,1,...). После выхода из цикла выполните проверку родительского цикла для этого значения. Позвольте мне продемонстрировать:

breaker = False #our mighty loop exiter!
while True:
    while True:
        if conditionMet:
            #insert code here...
            breaker = True 
            break
    if breaker: # the interesting part!
        break   # <--- !

Если у вас бесконечный цикл, это единственный выход; для других циклов выполнение действительно намного быстрее. Это также работает, если у вас много вложенных циклов. Вы можете выйти из всех, или только несколько. Безграничные возможности! Надеюсь, это помогло!

Я склонен согласиться с тем, что рефакторинг в функцию, как правило, является лучшим подходом для такого рода ситуаций, но для случаев, когда вам действительно нужно вырваться из вложенных циклов, вот интересный вариант подхода, вызывающего исключения, который описал @S.Lott. Он использует Python with Заявление, чтобы повышение исключений выглядело немного лучше. Определите новый менеджер контекста (вы должны сделать это только один раз) с помощью:

from contextlib import contextmanager
@contextmanager
def nested_break():
    class NestedBreakException(Exception):
        pass
    try:
        yield NestedBreakException
    except NestedBreakException:
        pass

Теперь вы можете использовать этот менеджер контекста следующим образом:

with nested_break() as mylabel:
    while True:
        print "current state"
        while True:
            ok = raw_input("Is this ok? (y/n)")
            if ok == "y" or ok == "Y": raise mylabel
            if ok == "n" or ok == "N": break
        print "more processing"

Преимущества: (1) это немного чище (без явного блока try-кроме), и (2) вы получаете пользовательский Exception подкласс для каждого использования nested_break; не нужно объявлять свой Exception подкласс каждый раз.

Во-первых, вы можете также рассмотреть вопрос о том, чтобы сделать процесс получения и проверки входных данных функцией; внутри этой функции вы можете просто вернуть значение, если оно правильное, и продолжать вращаться в цикле while, если нет. По сути, это устраняет проблему, которую вы решили, и обычно может применяться в более общем случае (разрыв нескольких циклов). Если вам абсолютно необходимо сохранить эту структуру в вашем коде, и вы действительно не хотите иметь дело с булевыми значениями бухгалтерии...

Вы также можете использовать goto следующим образом (используя модуль April Fools отсюда):

#import the stuff
from goto import goto, label

while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": goto .breakall
        if ok == "n" or ok == "N": break
    #do more processing with menus and stuff
label .breakall

Я знаю, я знаю, "ты не должен использовать goto" и все такое, но это хорошо работает в странных случаях, подобных этому.

Чтобы разорвать несколько вложенных циклов без рефакторинга в функцию, используйте "симулированный оператор goto" со встроенным исключением StopIteration:

try:
    for outer in range(100):
        for inner in range(100):
            if break_early():
                raise StopIteration

except StopIteration: pass

См. Это обсуждение использования операторов goto для разрыва вложенных циклов.


keeplooping=True
while keeplooping:
    #Do Stuff
    while keeplooping:
          #do some other stuff
          if finisheddoingstuff(): keeplooping=False

или что-то типа того. Вы можете установить переменную во внутреннем цикле и проверить ее во внешнем цикле сразу после выхода из внутреннего цикла, прервав при необходимости. Мне нравится метод GOTO, при условии, что вы не возражаете против использования модуля первоапрельской шутки - он не Pythonic, но он имеет смысл.

Это не самый красивый способ сделать это, но, на мой взгляд, это лучший способ.

def loop():
    while True:
    #snip: print out current state
        while True:
            ok = get_input("Is this ok? (y/n)")
            if ok == "y" or ok == "Y": return
            if ok == "n" or ok == "N": break
        #do more processing with menus and stuff

Я почти уверен, что и здесь вы можете что-то придумать, используя рекурсию, но я не знаю, подходит ли вам этот вариант.

Невозможно сделать это на уровне языка. В некоторых языках есть goto, в других есть пауза, которая принимает аргумент, а в python нет.

Лучшие варианты:

  1. Установите флаг, который проверяется внешним циклом, или установите условие внешнего цикла.

  2. Поместите цикл в функцию и используйте return для одновременного выхода из всех циклов.

  3. Переформулируйте свою логику.

Благодарим Вивека Нагараджана, программиста с 1987 года.


Использование функции

def doMywork(data):
    for i in data:
       for e in i:
         return 

Использование флага

is_break = False
for i in data:
   if is_break:
      break # outer loop break
   for e in i:
      is_break = True
      break # inner loop break

И почему бы не продолжать цикл, если выполняются два условия? Я думаю, что это более питонический способ:

dejaVu = True

while dejaVu:
    while True:
        ok = raw_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y" or ok == "n" or ok == "N":
            dejaVu = False
            break

Не так ли?

Всего наилучшего.

Разложите логику вашего цикла в итератор, который возвращает переменные цикла и возвращает, когда все готово - вот простое, которое выкладывает изображения в строках / столбцах, пока у нас не будет изображений или нет мест для их размещения:

def it(rows, cols, images):
    i = 0
    for r in xrange(rows):
        for c in xrange(cols):
            if i >= len(images):
                return
            yield r, c, images[i]
            i += 1 

for r, c, image in it(rows=4, cols=4, images=['a.jpg', 'b.jpg', 'c.jpg']):
    ... do something with r, c, image ...

Это имеет преимущество разделения сложной логики цикла и обработки...

Простой способ превратить несколько циклов в один разрывный цикл - это использовать numpy.ndindex

for i in range(n):
  for j in range(n):
    val = x[i, j]
    break # still inside the outer loop!

for i, j in np.ndindex(n, n):
  val = x[i, j]
  break # you left the only loop there was!

Вы должны индексировать свои объекты, в отличие от возможности явно перебирать значения, но, по крайней мере, в простых случаях это кажется примерно в 2-20 раз проще, чем большинство предлагаемых ответов.

В Питоне есть скрытый трюк while ... else структура, которая может использоваться для имитации двойного разрыва без особых изменений / дополнений кода. По сути, если while условие ложное, else блок сработал. Ни исключения, continue или же break вызвать else блок. Для получения дополнительной информации см. Ответы на " Предложение Else для Python while Statement " или документацию Python для while (v2.7).

while True:
    #snip: print out current state
    ok = ""
    while ok != "y" and ok != "n":
        ok = get_input("Is this ok? (y/n)")
        if ok == "n" or ok == "N":
            break    # Breaks out of inner loop, skipping else

    else:
        break        # Breaks out of outer loop

    #do more processing with menus and stuff

Единственным недостатком является то, что вам нужно переместить условие двойного разрыва в while условие (или добавить переменную флага). Вариации этого существуют и для for петля, где else блок срабатывает после завершения цикла.

Используя функцию:

def myloop():
    for i in range(1,6,1):  # 1st loop
        print('i:',i)
        for j in range(1,11,2):  # 2nd loop
            print('   i, j:' ,i, j)
            for k in range(1,21,4):  # 3rd loop
                print('      i,j,k:', i,j,k)
                if i%3==0 and j%3==0 and k%3==0:
                    return  # getting out of all loops

myloop()

Попробуйте запустить приведенные выше коды, комментируя return также.

Без использования какой-либо функции:

done = False
for i in range(1,6,1):  # 1st loop
    print('i:', i)
    for j in range(1,11,2):  # 2nd loop
        print('   i, j:' ,i, j)
        for k in range(1,21,4):  # 3rd loop
            print('      i,j,k:', i,j,k)
            if i%3==0 and j%3==0 and k%3==0:
                done = True
                break  # breaking from 3rd loop
        if done: break # breaking from 2nd loop
    if done: break     # breaking from 1st loop

Теперь запустите приведенные выше коды как есть, а затем попробуйте запустить, закомментировав каждую строку, содержащую break по одному снизу.

В этом случае, как отмечают и другие, функциональная декомпозиция - это путь. Код в Python 3:

def user_confirms():
    while True:
        answer = input("Is this OK? (y/n) ").strip().lower()
        if answer in "yn":
            return answer == "y"

def main():
    while True:
        # do stuff
        if user_confirms():
            break

Я хотел бы напомнить вам, что функции в Python могут быть созданы прямо в середине кода и могут прозрачно обращаться к окружающим переменным для чтения и nonlocal или же global декларация для написания.

Таким образом, вы можете использовать функцию в качестве "разрушаемой структуры управления", определяя место, куда вы хотите вернуться:

def is_prime(number):

    foo = bar = number

    def return_here():
        nonlocal foo, bar
        init_bar = bar
        while foo > 0:
            bar = init_bar
            while bar >= foo:
                if foo*bar == number:
                    return
                bar -= 1
            foo -= 1

    return_here()

    if foo == 1:
        print(number, 'is prime')
    else:
        print(number, '=', bar, '*', foo)

>>> is_prime(67)
67 is prime
>>> is_prime(117)
117 = 13 * 9
>>> is_prime(16)
16 = 4 * 4

Другой способ сократить вашу итерацию до одноуровневого цикла - использовать генераторы, как также указано в ссылке на Python.

for i, j in ((i, j) for i in A for j in B):
    print(i , j)
    if (some_condition):
        break

Вы можете масштабировать его до любого количества уровней для цикла

Недостатком является то, что вы больше не можете пробить только один уровень. Это все или ничего.

Другим недостатком является то, что он не работает с циклом while. Первоначально я хотел опубликовать этот ответ на Python - `break` из всех циклов, но, к сожалению, он закрыт как дубликат этого

break_label = None
while True:
    # snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y":
            break_label = "outer"   # specify label to break to
            break
        if ok == "n" or ok == "N":
            break
    if break_label:
        if break_label != "inner":
            break                   # propagate up
        break_label = None          # we have arrived!
if break_label:
    if break_label != "outer":
        break                       # propagate up
    break_label = None              # we have arrived!

#do more processing with menus and stuff

Попробуйте использовать бесконечный генератор.

from itertools import repeat
inputs = (get_input("Is this ok? (y/n)") for _ in repeat(None))
response = (i.lower()=="y" for i in inputs if i.lower() in ("y", "n"))

while True:
    #snip: print out current state
    if next(response):
        break
    #do more processing with menus and stuff
break_levels = 0
while True:
    # snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y":
            break_levels = 1        # how far nested, excluding this break
            break
        if ok == "n" or ok == "N":
            break                   # normal break
    if break_levels:
        break_levels -= 1
        break                       # pop another level
if break_levels:
    break_levels -= 1
    break

# ...and so on

Моя причина приехать сюда в том, что у меня был внешний цикл и внутренний цикл, например, так:

for x in array:
  for y in dont_use_these_values:
    if x.value==y:
      array.remove(x)  # fixed, was array.pop(x) in my original answer
      continue

  do some other stuff with x

Как видите, на самом деле он не перейдет к следующему x, а вместо этого перейдет к следующему y.

то, что я нашел, чтобы решить эту проблему, это просто дважды запустить массив:

for x in array:
  for y in dont_use_these_values:
    if x.value==y:
      array.remove(x)  # fixed, was array.pop(x) in my original answer
      continue

for x in array:
  do some other stuff with x

Я знаю, что это был конкретный случай вопроса ОП, но я публикую его в надежде, что он поможет кому-то по-другому взглянуть на свою проблему, сохраняя при этом простоту.

Лично я бы использовал логическое значение, которое переключается, когда я готов разорвать внешний цикл. Например

      while True:
    #snip: print out current state
    quit = False
    while True:
        ok = input("Is this ok? (y/n)")
        if ok.lower() == "y":
            quit = True
            break # this should work now :-)
        if ok.lower() == "n":
            quit = True
            break # This should work too :-)
    if quit:
        break
    #do more processing with menus and stuff

Решения в 2 пути

С примером: эти две матрицы равны / одинаковы?
matrix1 и matrix2 имеют одинаковый размер, n, 2 размерных матрицы.

Первое решение, без функции

same_matrices = True
inner_loop_broken_once = False
n = len(matrix1)

for i in range(n):
    for j in range(n):

        if matrix1[i][j] != matrix2[i][j]:
            same_matrices = False
            inner_loop_broken_once = True
            break

    if inner_loop_broken_once:
        break

Второе решение, с функцией
Это окончательное решение для моего случая

def are_two_matrices_the_same (matrix1, matrix2):
    n = len(matrix1)
    for i in range(n):
        for j in range(n):
            if matrix1[i][j] != matrix2[i][j]:
                return False
    return True

Хорошего дня!

Вот реализация, которая, кажется, работает:

break_ = False
for i in range(10):
    if break_:
        break
    for j in range(10):
        if j == 3:
            break_ = True
            break
        else:
            print(i, j)

Единственный недостаток в том, что вам нужно определить break_ перед петлями.

Вы можете определить переменную (например, break_statement), а затем изменить ее на другое значение при возникновении условия двух разрывов и использовать его в операторе if для выхода из второго цикла.

while True:
    break_statement=0
    while True:
        ok = raw_input("Is this ok? (y/n)")
        if ok == "n" or ok == "N": 
            break
        if ok == "y" or ok == "Y": 
            break_statement=1
            break
    if break_statement==1:
        break

Вероятно, подойдет маленький трюк, как показано ниже, если не предпочитаете рефакториал в функцию

добавлена ​​1 переменная break_level для управления условием цикла while

break_level = 0
# while break_level < 3: # if we have another level of nested loop here
while break_level < 2:
    #snip: print out current state
    while break_level < 1:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": break_level = 2 # break 2 level
        if ok == "n" or ok == "N": break_level = 1 # break 1 level

Недавно я столкнулся с этим и, желая избежать повторяющегося оператора return, который может скрывать логические ошибки, рассмотрел идею @yak. Это хорошо работает во вложенных циклах for, но не очень элегантно. Альтернативой является проверка условия перед следующим циклом:

      b = None
for a in range(10):
    if something(a, b): # should never = True if b is None
        break
    for b in range(20):
        pass

Это может работать не везде, но может быть адаптировано и, при необходимости, имеет то преимущество, что позволяет дублировать условие, а не потенциальный результат.

Надеюсь, это поможет:

x = True
y = True
while x == True:
    while y == True:
         ok = get_input("Is this ok? (y/n)") 
         if ok == "y" or ok == "Y":
             x,y = False,False #breaks from both loops
         if ok == "n" or ok == "N": 
             break #breaks from just one
Другие вопросы по тегам