2008-10-10 2 views
300

Учитывая следующий код (который не работает):Как вырваться из нескольких циклов в 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()

+4

Исключения не связаны, если это не какая-то странная идиома Python, которую мне еще предстоит узнать ... –

+1

@monoxide: Исключения, похоже, ведут себя как специализированный goto, который выпрыгивает из нормального выполнения в содержащий блок try. Чувствует себя немного похожим на меня. –

+1

См. Мой другой вопрос через некоторое время (http://stackoverflow.com/questions/174458/exceptions-for-flow-control), почти все согласились с тем, что это плохая идея, и это было потенциально законным использованием. –

ответ

336

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

+2

Это еще одна мысль, которую я имел, так как функция get_input_yn() была бы полезна и в другом месте, я уверен. –

+49

согласны в этом конкретном случае, но в общем случае ретрансляции «Я вложенные циклы, что мне делать» может не иметь смысла. –

+0

, используя исключение, может быть проще, когда вы должны уступить вместо использования возврата, однако вы, вероятно, должны использовать itertools.islice() в таком случае. –

91

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

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

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» нам нужен немедленный выход. Это более необычно.

+1

Я только начал изучать python прошлой ночью, поэтому заранее извиняюсь за то, что не думал о таких вещах, как «ok in ...». некоторые хорошие предложения здесь, но действительно ли это исключение? Для пользователя, принимающего вход, нет ничего исключительного. –

+0

Конечно, для этого кода требуется предложение «except». –

+0

@monoxide - это исключение из предположения, что пользователь войдет в плохой ответ. Действительно, исключения на Python дешевы и полезны. Не стесняйтесь! –

34

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

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

#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» и все такое, но она хорошо работает в странных случаях, подобных этому.

+0

Я сейчас в колледже, быстро (так как goto's не в модульном индексе), для чего требуется? –

+1

Если это что-то вроде команды COME FROM в INTERCAL, тогда ничего нет –

+0

comefrom в Python позволяет перенаправить запущенную программу в другое место, когда она достигает определенной метки. Здесь больше информации (http://entrian.com/goto/) –

10

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

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

+0

Это своего рода установка флага! – SIslam

8

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

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 

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

123

PEP 3136 предлагает пометку break/continue. Guido rejected it потому что «код настолько сложный, что требуется эта функция, очень редка». PEP упоминает некоторые обходные пути (например, метод исключения), в то время как Guido считает, что рефакторинг для использования возврата в большинстве случаев будет проще.

+50

Хотя рефактор/'return', как правило, путь, я видел довольно много случаев, когда простое краткое выражение 'break 2' 'будет просто иметь такой смысл. Кроме того, refactor/'return' не работает одинаково для' continue'. В этих случаях числовой разрыв и продолжение будут проще отслеживать и менее загромождать, чем рефакторинг, до крошечной функции, поднимая исключения или запутанную логику, включающую установку флага для разрыва на каждом уровне гнезда. Жаль, что Гидо отверг это. –

+4

'break; ломать "было бы хорошо. – PyRulez

+3

@PyRulez Это так же сложно, как введение брекетов. – simonzack

8

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

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

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

104

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

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

Это не будет работать, если внутренний цикл бесконечен, например. 'для x в itertools.count (1): ...'. –

+5

@eugeney Почему бы и нет? Первый разрыв выйдет из внутреннего цикла. – Navin

+2

@Navin, потому что в этом случае может быть только одна итерация внешнего цикла. –

43

Я склонен согласиться, что рефакторинг в функцию, как правило, наилучший подход к такого рода ситуации, но когда вы действительно потребность вырваться из вложенных циклов, вот интересный вариант exception- который был описан С. Лонттом. Он использует инструкцию 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-except), и (2) вы получаете специальный подкласс Exception для каждого использования nested_break; нет необходимости объявлять свой собственный Exception подкласс каждый раз.

21

Ввести новую переменную, которую вы будете использовать как «выключатель петли». Сначала присвойте ему что-то (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 # <--- ! 

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

+2

Вопрос заключается в том, чтобы избежать такого беспорядка. –

1

Аналогично предыдущему, но более компактному. (Булевы просто цифры)

breaker = False #our mighty loop exiter! 
while True: 
    while True: 
     ok = get_input("Is this ok? (y/n)") 
     breaker+= (ok.lower() == "y") 
     break 

    if breaker: # the interesting part! 
     break # <--- ! 
+0

Почему downvote? –

+1

Это выглядит довольно уродливо и делает код сложнее понять, по сравнению с предыдущим. Кроме того, это неправильно. Он пропускает фактическую проверку, является ли вход приемлемым и прерывается после 1 цикла. – Eric

2

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

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 

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

+0

Возможно, это не Python. Каков тип массива? Возможно, список, но что он содержит? Даже если он содержит ints, array.pop (x), вероятно, не будет делать то, что вы хотите. – Veky

+0

Это хороший момент. Я не могу найти код, на который я ссылался. Для тех, кто это читает, array.pop (i) «Удаляет элемент с индексом i из массива и возвращает его». согласно документации на python. Поэтому нужно было бы получить индекс элемента x в массиве, чтобы этот код работал должным образом. Существует также функция array.remove (x), которая будет делать то, что ожидается. Я изменю свой ответ выше, чтобы исправить эту ошибку. Это предполагает, что второй массив не содержит дубликатов, так как array.remove (x) удалит только первый экземпляр x. –

+0

Хорошо, тогда я понял.В этом случае просто использование 'break' вместо' continue' будет делать то, что вы хотите, не так ли? :-) – Veky

7

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

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 

Не так ли?

Все самое лучшее.

+0

почему бы не просто 'while dejaVu:'? Вы все равно установили его. –

+0

эй, что работает! Я думал в двух условиях «Истинный», чтобы пропустить две петли, но достаточно одного. –

+1

@MatthewScharley Я думаю, это должно показать, что это работает в вложенных циклах. – handle

0

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

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

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

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

этот разрывается до определенного «ярлыка» – RufusVS

3

В этом случае, как указывалось другими, а также, функциональное разложение путь. Код в 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 
3

Существует скрытая уловка в while ... else структуре Python, который может быть использован для имитации двойного разрыва без особых изменений кода/добавлений. В сущности, если условие while является ложным, срабатывает блок else. Не исключено, что continue или break запускают блок else. Для получения дополнительной информации см. Ответы на «Else clause on Python while statement», или Python doc on 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 запускается после завершения цикла.

+0

Это, похоже, не соответствует требованию двойных перерывов. Работает для конкретной заданной проблемы, но не для актуального вопроса. – Dakkaron

+0

@Dakkaron Вы уверены, что поняли этот код правильно? Код действительно решает вопрос OPs и ломается подобно запросу. Однако он не выходит из нескольких циклов, но использует предложение else, чтобы заменить необходимость удвоения разрыва. – holroy

+0

С моей точки зрения, вопрос: «Как вырваться из нескольких циклов в Python?», И ответ должен был быть «Это не работает, попробуйте что-то еще». Я знаю, что он фиксирует точный приведенный пример OP, но не отвечает на их вопрос. – Dakkaron

1

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

добавляют 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 
2

Другой способ уменьшения вашей итерации к одноуровневой цикл будет осуществляться через использование генераторов, также указанные в python reference

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

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

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

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

+1

Он также работает для циклов while, вам нужно только написать генератор как def (с выходом), а не как понимание. – Veky

+0

Да, [докладчик в PyCon утверждает здесь] (https://youtu.be/EnSu9hHqq5o?t=19m38s), что даже принятый ответ Роберта Россни не является действительно Pythonic, но генератор - это правильный способ разбить несколько циклов. (Я бы рекомендовал смотреть все видео!) – Post169

-5

Это выйдет из три времени петли

while True: 
    while True: 
     while True: 
      print("get me out") 
      break 
     break 
    break 
+2

@beak, я думаю, что это вообще не отвечает на вопрос OP. То, что хочет OP, состоит в том, чтобы уйти изнутри нескольких 'while' только в том случае, если какое-либо условие выполнено и в других условиях продолжить внешнюю часть двух циклов. Я не понимаю, как это сделать. – SergGr

0

Вы можете определить переменные (например 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 
0

Я хотел бы напомнить вам, что функции в 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 
0

Надеется, что это помогает:

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 
0

Поскольку этот вопрос стал стандартным вопросом для взлома в конкретный цикл, я хотел бы привести свой ответ на примере, используя Exception.

Несмотря на отсутствие метки с именем break of loop в конструкции с многострочным циклом, мы можем использовать User-defined Exceptions для перехода в конкретный цикл по нашему выбору. Рассмотрим следующий пример, в котором дайте нам печатать все числа ДО 4 цифры в базовой 6 системы нумерации:

class BreakLoop(Exception): 
    def __init__(self, counter): 
     Exception.__init__(self, 'Exception 1') 
     self.counter = counter 

for counter1 in range(6): # Make it 1000 
    try: 
     thousand = counter1 * 1000 
     for counter2 in range(6): # Make it 100 
      try: 
       hundred = counter2 * 100 
       for counter3 in range(6): # Make it 10 
        try: 
         ten = counter3 * 10 
         for counter4 in range(6): 
          try: 
           unit = counter4 
           value = thousand + hundred + ten + unit 
           if unit == 4 : 
            raise BreakLoop(4) # Don't break from loop 
           if ten == 30: 
            raise BreakLoop(3) # Break into loop 3 
           if hundred == 500: 
            raise BreakLoop(2) # Break into loop 2 
           if thousand == 2000: 
            raise BreakLoop(1) # Break into loop 1 

           print('{:04d}'.format(value)) 
          except BreakLoop as bl: 
           if bl.counter != 4: 
            raise bl 
        except BreakLoop as bl: 
         if bl.counter != 3: 
          raise bl 
      except BreakLoop as bl: 
       if bl.counter != 2: 
        raise bl 
    except BreakLoop as bl: 
     pass 

Когда мы выводим вывод, что мы никогда не получит никакого значения которого блок место с 4. В этом случае, мы не отрываемся от какой-либо петли, так как BreakLoop(4) поднят и пойман в одном цикле. Аналогично, всякий раз, когда десять мест имеют 3, мы пробиваем третий цикл, используя BreakLoop(3). Всякий раз, когда сто места имеют 5, мы ломаемся во вторую петлю, используя BreakLoop(2), и когда тысяча мест имеет 2, мы пробиваем первый цикл, используя BreakLoop(1).

Короче говоря, поднимите свое исключение (встроенное или определенное пользователем) во внутренние циклы и поймайте его в цикле, откуда вы хотите возобновить управление. Если вы хотите выйти из всех циклов, поймите Exception вне всех циклов. (Я не показал этот случай в примере).

6

Чтобы вырваться из нескольких вложенных циклов, без рефакторинга в функцию, использовать в «моделируемой заявлении Гото» с встроенной StopIteration exception:

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

except StopIteration: pass 

См this discussion на использование GOTO заявления для вырывание вложенных циклов.

0

Все вышесказанное является отличным ответом! Я нахожу следующее полезным.

Должно быть вставлено следующее: если требуется условие отключения всех петель.

import sys 
sys.exit() 
0

Способ, которым я решаю это, - определить переменную, на которую ссылаются, чтобы определить, перешли ли вы на следующий уровень или нет.

Variable_That_Counts_To_Three=1 
while 1==1: 
    shouldbreak='no' 
    Variable_That_Counts_To_Five=0 
    while 2==2: 
     Variable_That_Counts_To_Five+=1 
     print(Variable_That_Counts_To_Five) 
     if Variable_That_Counts_To_Five == 5: 
      if Variable_That_Counts_To_Three == 3: 
       shouldbreak='yes' 
      break 
    print('Three Counter = ' + str(Variable_That_Counts_To_Three)) 
    Variable_That_Counts_To_Three+=1 
    if shouldbreak == 'yes': 
     break 

print(''' 
This breaks out of two loops!''') 

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

Смежные вопросы