Python: continuando para a próxima iteração no loop externo

135

Eu queria saber se existem maneiras internas de continuar a próxima iteração no loop externo em python. Por exemplo, considere o código:

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

Eu quero que essa instrução continue saia do loop jj e vá para o próximo item no loop ii. Eu posso implementar essa lógica de alguma outra maneira (definindo uma variável flag), mas existe uma maneira fácil de fazer isso ou é como pedir demais?

Sahas
fonte
11
Na verdade, existe uma declaração goto que funciona para Python: entrian.com/goto . Foi lançado como uma piada de abril :-), mas deve funcionar.
Codeape 7/12/2009
3
Oh, por favor, não use essa piada! É notavelmente inteligente, mas você ficará triste mais tarde se você o colocar no seu código.
Ned Batchelder

Respostas:

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

Em geral, quando você tem vários níveis de loop e breaknão funciona para você (porque deseja continuar com um dos loops superiores, não aquele logo acima do atual), você pode executar um dos seguintes procedimentos

Refatorar os loops dos quais você deseja escapar para uma função

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


for i in ...:
    inner()

A desvantagem é que você pode precisar passar para essa nova função algumas variáveis, que estavam anteriormente no escopo. Você pode apenas passá-los como parâmetros, transformá-los em variáveis ​​de instância em um objeto (crie um novo objeto apenas para esta função, se isso fizer sentido) ou variáveis ​​globais, singletons, o que for (ehm, ehm).

Ou você pode definir innercomo uma função aninhada e permitir apenas capturar o que precisa (pode ser mais lento?)

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

Use exceções

Filosoficamente, é para isso que servem as exceções, interrompendo o fluxo do programa através dos blocos de construção da programação estruturada (se, por, enquanto) quando necessário.

A vantagem é que você não precisa dividir o único pedaço de código em várias partes. Isso é bom se for algum tipo de cálculo que você está projetando enquanto o escreve em Python. A introdução de abstrações nesse ponto inicial pode atrasá-lo.

O lado ruim dessa abordagem é que os autores de intérpretes / compiladores geralmente assumem que as exceções são excepcionais e as otimizam de acordo.

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

Crie uma classe de exceção especial para isso, para não correr o risco de silenciar acidentalmente alguma outra exceção.

Algo completamente diferente

Estou certo de que ainda existem outras soluções.

user7610
fonte
Não posso acreditar que não pensei em mudar meu segundo loop para outro método. Estou ficando lento
pmccallum
1
Para mim, o uso de exceções é uma boa maneira de conseguir isso. Concordo com @ user7610 - "filosoficamente, é para isso que existem exceções".
Renato Byrro 23/06/19
Boa variável booleana antiga e instruções If?
MrR 12/06
O OP está procurando soluções alternativas para isso: "Eu posso implementar essa lógica de outra maneira (definindo uma variável flag) [...]"
user7610
149
for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            break
    else:
        ...block1...

Break interromperá o loop interno e o bloco1 não será executado (ele será executado apenas se o loop interno for encerrado normalmente).

culebrón
fonte
1
Oi, existem outras opções como essa? Porque eu quero fazer outro loop for no bloco1, e assim meu código iria 3 níveis de profundidade. Situação estranha.
Sahas
3
Para mim, isso soa como você está tentando fazer algo com loops que seria melhor abordados de uma maneira diferente ...
Kimvais
Sim. Por isso não usei a estrutura for..else. Agora eu ainda precisaria de loops, mas usarei variáveis ​​de flag para desviar o controle.
Sahas
3
for...elsegeralmente é uma construção útil, embora possa ser confusa. Lembre-se de que elsesignifica "sem interrupção" neste contexto.
asmeurer 24/03
Isso parece estar limitado a apenas duas camadas de loops. Preciso atualizar algum código que possua três loops aninhados, e um novo requisito do cliente significa que, em determinadas circunstâncias, o loop mais interno precisa continuar a próxima iteração do loop mais externo. Presumo que sua sugestão não se aplique a esse cenário.
kasperd
42

Em outros idiomas, você pode rotular o loop e interromper o loop rotulado. A Proposta de Aprimoramento do Python (PEP) 3136 sugeriu adicioná-los ao Python, mas Guido o rejeitou :

No entanto, estou rejeitando com base em que códigos tão complicados para exigir esse recurso são muito raros. Na maioria dos casos, existem soluções alternativas existentes que produzem código limpo, por exemplo, usando 'return'. Embora eu tenha certeza de que existem alguns casos reais (raros) em que a clareza do código sofreria uma refatoração que possibilita o uso de retorno, isso é compensado por dois problemas:

  1. A complexidade adicionada ao idioma, permanentemente. Isso afeta não apenas todas as implementações do Python, mas também todas as ferramentas de análise de código-fonte, além de toda a documentação da linguagem.

  2. Minha expectativa de que o recurso seja abusado mais do que será usado corretamente, levando a uma diminuição líquida na clareza do código (medida em todo o código Python escrito a partir de agora). Programadores preguiçosos estão por toda parte e, antes que você perceba, você tem uma bagunça incrível nas mãos, com código ininteligível.

Portanto, se é isso que você estava esperando, você está sem sorte, mas olhe para uma das outras respostas, pois há boas opções lá.

Dave Webb
fonte
4
Interessante. Eu concordo com Guido aqui. Embora fosse bom para alguns casos, seria abusado. Outro motivo para não implementá-lo é que, no momento, é bastante simples encaminhar o código de porta entre C e Python. Uma vez que o Python começa a captar recursos que faltam em outros idiomas, isso se torna mais difícil. Tomemos, por exemplo, o fato de que você pode ter uma instrução else em um loop for no Python ... isso torna o código menos portátil para outras linguagens.
Eric.frederich
2
Todos granizo Guido nosso BDFL
JnBrymn
4
Isso é mais um assédio moral do que um bom contra-argumento, mas parece-me que o comportamento de for-elseé mais complicado, mais difícil de ler e provavelmente mais abusado (se não um erro total) do que os loops nomeados. Eu acho que eu teria usado uma palavra-chave diferente do que else- talvez algo como resumeteria sido bom? Você breakno loop e resumeé logo depois?
ArtOfWarfare
5
Isso me deixa triste. Não consigo acreditar em como amo e odeio Python ao mesmo tempo. Tão bonito, mas tão wtf.
Jlh
5
@ jlh Principalmente wtf para mim. Às vezes acho que quer ser diferente não para fins legítimos, mas apenas para ser diferente. Este é um bom exemplo disso. Me deparo com a necessidade de quebrar loops externos com bastante frequência.
Rikaelus # 24/17
14

Eu acho que você poderia fazer algo assim:

for ii in range(200):
    restart = False
    for jj in range(200, 400):
        ...block0...
        if something:
            restart = True
            break
    if restart:
        continue
    ...block1...
asmeurer
fonte
4
-1: O OP afirmou claramente que eles sabiam que poderiam fazer algo assim, e isso parece uma versão mais confusa da resposta aceita (que antecede a sua por 8 meses, portanto não poderia ter sido que você perdeu a resposta aceita responda).
ArtOfWarfare
10
A resposta aceita é não mais claro se você nunca viu for, elseantes (e eu acho que mesmo a maioria das pessoas que têm não me lembro fora do topo da sua cabeça como ele funciona).
asmeurer
3

Eu acho que uma das maneiras mais fáceis de conseguir isso é substituir a declaração "continue" por "break", ou seja,

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

Por exemplo, aqui está o código fácil para ver exatamente como isso acontece:

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")
Khelina Fedorchuk
fonte
2

Outra maneira de lidar com esse tipo de problema é usar Exception ().

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

Por exemplo:

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

resultado:

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

Supondo que queremos pular para o loop n externo a partir do loop m se 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

resultado:

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

Link de referência: http://www.programming-idioms.org/idiom/42/continue-outer-loop/1264/python

Patrick
fonte
1

Queremos encontrar algo e parar a iteração interna. Eu uso um sistema de bandeira.

for l in f:
    flag = True
    for e in r:
        if flag==False:continue
        if somecondition:
            do_something()
            flag=False
Esther
fonte
Não sei por que sua solução foi reduzida; alguém postou basicamente exatamente a mesma coisa e ficou upvoted 10 vezes
Locane
Não tenho muita sorte com o stackoverflow.
Esther
1
Talvez porque já exista basicamente exatamente a mesma coisa aqui, acho ... E a False:continuecoisa é ... formatação incomum. Como costuma ser o caso em sistemas "naturais" onde exponencial é a norma, você só precisa ter sorte algumas vezes no SO para acumular uma quantidade significativa de pontos de reputação. De qualquer forma, minhas "melhores" respostas são geralmente as menos populares.
user7610
0

Eu apenas fiz algo assim. Minha solução para isso foi substituir o interior do loop por uma compreensão da lista.

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

onde op é algum operador booleano que atua em uma combinação de ii e jj. No meu caso, se alguma das operações retornou verdadeira, eu terminei.

Isso realmente não é tão diferente de quebrar o código em uma função, mas pensei que era interessante usar o operador "any" para fazer um OR lógico em uma lista de booleanos e fazer a lógica em uma única linha. Também evita a chamada de função.

cagem12
fonte