Как вырваться из нескольких циклов в 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 нет.
Лучшие варианты:
Установите флаг, который проверяется внешним циклом, или установите условие внешнего цикла.
Поместите цикл в функцию и используйте return для одновременного выхода из всех циклов.
Переформулируйте свою логику.
Благодарим Вивека Нагараджана, программиста с 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