Python: переход к следующей итерации во внешнем цикле

136

Я хотел знать, есть ли какие-либо встроенные способы продолжить следующую итерацию во внешнем цикле в python. Например, рассмотрим код:

for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            continue
    ...block1...

Я хочу, чтобы этот оператор continue выходил из цикла jj и переходил к следующему элементу в цикле ii. Я могу реализовать эту логику каким-либо другим способом (установив переменную флага), но есть ли простой способ сделать это или это все равно что просить слишком много?

Sahas
источник
11
На самом деле существует рабочий оператор goto для Python: entrian.com/goto . Он был выпущен как первоапрельская шутка :-), но должен работать.
codeape
3
О, пожалуйста, не используй эту шутку о goto! Это замечательно умно, но позже вам будет грустно, если вы добавите его в свой код.
Нед Батчелдер,

Ответы:

71
for i in ...:
    for j in ...:
        for k in ...:
            if something:
                # continue loop i

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

Реорганизуйте циклы, из которых вы хотите выйти, в функцию

def inner():
    for j in ...:
        for k in ...:
            if something:
                return


for i in ...:
    inner()

Недостатком является то, что вам может потребоваться передать этой новой функции некоторые переменные, которые ранее были в области видимости. Вы можете либо просто передать их как параметры, сделать их экземплярами переменных для объекта (создать новый объект только для этой функции, если это имеет смысл), либо глобальные переменные, синглтоны, что угодно (эхм, эм).

Или вы можете определить innerкак вложенную функцию и позволить ей просто фиксировать то, что ей нужно (может быть медленнее?)

for i in ...:
    def inner():
        for j in ...:
            for k in ...:
                if something:
                    return
    inner()

Используйте исключения

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

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

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

class ContinueI(Exception):
    pass


continue_i = ContinueI()

for i in ...:
    try:
        for j in ...:
            for k in ...:
                if something:
                    raise continue_i
    except ContinueI:
        continue

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

Что-то совсем другое

Я уверен, что есть и другие решения.

user7610
источник
Не могу поверить, что не подумал о переводе второго цикла на другой метод. Я замедляюсь
pmccallum
1
Для меня использование исключений - хороший способ добиться этого. Я согласен с @ user7610 - «философски это то, для чего нужны исключения».
Renato Byrro
Старая добрая логическая переменная и операторы If?
MrR
OP ищет альтернативные решения: «Я могу реализовать эту логику другим способом (установив переменную флага) [...]»
user7610
149
for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            break
    else:
        ...block1...

Break прервет внутренний цикл, и блок 1 не будет выполнен (он будет запущен, только если внутренний цикл завершится нормально).

culebrón
источник
1
Привет, есть еще такие варианты? Потому что я хочу сделать еще один цикл for в блоке 1, и так мой код будет на 3 уровня. Странная ситуация.
Сахас, 07
3
Для меня это звучит так, как будто вы пытаетесь сделать что-то с циклами for, которые лучше всего подошли бы по-другому ...
Кимвайс
Да. Вот почему я не использовал структуру for..else. Теперь мне все еще нужны циклы, но я буду использовать флаговые переменные для переключения управления.
Сахас,
3
for...elseчасто бывает полезной конструкцией, хотя может сбивать с толку. Просто помните, что elseв данном контексте это означает «без перерыва».
asmeurer
Кажется, это ограничивается всего двумя слоями петель. Мне нужно обновить некоторый код, который имеет три вложенных цикла, и новое требование заказчика означает, что при определенных обстоятельствах самый внутренний цикл должен продолжить следующую итерацию самого внешнего цикла. Я предполагаю, что ваше предложение не применимо к этому сценарию.
kasperd
42

На других языках вы можете пометить цикл и выйти из помеченного цикла. Предложение по улучшению Python (PEP) 3136 предлагало добавить их в Python, но Гвидо отклонил его :

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

  1. Сложность добавлялась к языку навсегда. Это влияет не только на все реализации Python, но и на все инструменты анализа исходного кода, а также, конечно, на всю документацию по языку.

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

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

Дэйв Уэбб
источник
4
Интересный. Я согласен с Гвидо здесь. Хотя в некоторых случаях было бы неплохо, им злоупотребляли. Еще одна причина, по которой его не реализовали, заключается в том, что прямо сейчас довольно просто переносить код между C и Python. Когда Python начинает использовать функции, которых не хватает другим языкам, это становится труднее. Возьмем, к примеру, тот факт, что у вас может быть оператор else в цикле for в Python ... это делает код менее переносимым для других языков.
eric.frederich
2
Приветствую
4
Это скорее уловка, чем хороший контраргумент, но мне кажется, что его поведение for-elseсложнее, труднее читать и, вероятно, больше злоупотребляют (если не явной ошибкой), чем были бы именованные циклы. Думаю, я бы использовал другое ключевое слово, чем else- возможно, что-то вроде resumeбыло бы хорошо? Вы breakв курсе, и resumeэто сразу после него?
ArtOfWarfare
5
Это меня огорчает. Не могу поверить, как я люблю и ненавижу Python одновременно. Такая красивая, но такая фигня.
jlh
5
@jlh В основном, что за фиг для меня. Иногда мне кажется, что он хочет быть другим не для законных целей, а просто быть другим. Это хороший тому пример. Довольно часто сталкиваюсь с необходимостью порвать внешние петли.
Rikaelus
14

Я думаю, вы могли бы сделать что-то вроде этого:

for ii in range(200):
    restart = False
    for jj in range(200, 400):
        ...block0...
        if something:
            restart = True
            break
    if restart:
        continue
    ...block1...
asmeurer
источник
4
-1: ОП четко заявил, что они знали, что могут сделать что-то подобное, и это просто выглядит как более беспорядочная версия принятого ответа (который предшествует вашему на 8 месяцев, поэтому не могло быть, что вы просто пропустили принятый ответ ответ).
ArtOfWarfare
10
Принятый ответ не понятнее , если вы никогда не видели for, elseраньше (и я думаю , что даже большинство людей , которые не может вспомнить с верхней частью головы , как это работает).
asmeurer
3

Я думаю, что один из самых простых способов добиться этого - заменить «continue» оператором «break», т.е.

for ii in range(200):
 for jj in range(200, 400):
    ...block0...
    if something:
        break
 ...block1...       

Например, вот простой код, чтобы увидеть, как именно это происходит:

for i in range(10):
    print("doing outer loop")
    print("i=",i)
    for p in range(10):
        print("doing inner loop")
        print("p=",p)
        if p==3:
            print("breaking from inner loop")
            break
    print("doing some code in outer loop")
Хелина Федорчук
источник
2

Другой способ справиться с этой проблемой - использовать Exception ().

for ii in range(200):
    try:
        for jj in range(200, 400):
            ...block0...
            if something:
                raise Exception()
    except Exception:
        continue
    ...block1...

Например:

for n in range(1,4):
    for m in range(1,4):
        print n,'-',m

результат:

    1-1
    1-2
    1-3
    2-1
    2-2
    2-3
    3-1
    3-2
    3-3

Предполагая, что мы хотим перейти к внешнему циклу n из цикла m, если m = 3:

for n in range(1,4):
    try:
        for m in range(1,4):
            if m == 3:
                raise Exception()            
            print n,'-',m
    except Exception:
        continue

результат:

    1-1
    1-2
    2-1
    2-2
    3-1
    3-2

Ссылка для справки: http://www.programming-idioms.org/idiom/42/continue-outer-loop/1264/python

Патрик
источник
1

Мы хотим что-то найти, а затем остановить внутреннюю итерацию. Я использую систему флагов.

for l in f:
    flag = True
    for e in r:
        if flag==False:continue
        if somecondition:
            do_something()
            flag=False
Эстер
источник
Я не знаю, почему ваше решение было отклонено; кто-то опубликовал в основном то же самое и получил 10 голосов
Locane
Мне не очень повезло со stackoverflow.
Эстер
1
Может быть, потому, что здесь уже выложено в основном то же самое, я думаю ... И False:continueдело в ... необычном форматировании. Как это часто бывает в «естественных» системах, где экспоненциальные показатели являются нормой, вам нужно всего лишь несколько раз ударить SO, чтобы накопить значительное количество очков репутации. В любом случае, мои «лучшие» ответы обычно наименее популярны.
user7610
0

Я только что сделал что-то подобное. Моим решением было заменить внутренний цикл for на понимание списка.

for ii in range(200):
    done = any([op(ii, jj) for jj in range(200, 400)])
    ...block0...
    if done:
        continue
    ...block1...

где op - некоторый логический оператор, действующий на комбинацию ii и jj. В моем случае, если какая-либо из операций вернула истину, я был готов.

На самом деле это не сильно отличается от разбиения кода на функцию, но я подумал, что использование оператора «любой» для выполнения логического ИЛИ над списком логических значений и выполнения всей логики в одной строке было бы интересным. Это также позволяет избежать вызова функции.

cagem12
источник