Como posso entender a cláusula `else` dos loops do Python?

191

Muitos programadores de Python provavelmente não sabem que a sintaxe de whileloops e forloops inclui uma else:cláusula opcional :

for val in iterable:
    do_something(val)
else:
    clean_up()

O corpo da elsecláusula é um bom local para certos tipos de ações de limpeza e é executado na finalização normal do loop: returnOu seja, sair do loop com ou breakignorar a elsecláusula; saindo depois que um continueexecuta. Só sei disso porque procurei (mais uma vez), porque nunca consigo me lembrar de quando a elsecláusula é executada.

Sempre? Em "falha" do loop, como o nome sugere? Em terminação regular? Mesmo se o loop for encerrado comreturn ? Eu nunca posso ter certeza absoluta sem procurar.

Eu culpo minha persistente incerteza pela escolha da palavra-chave: acho elseincrivelmente unmnemônico para essa semântica. Minha pergunta não é "por que essa palavra-chave é usada para esse fim" (que eu provavelmente votaria para fechar, embora apenas depois de ler as respostas e os comentários), mas como posso pensar sobre a elsepalavra - chave para que sua semântica faça sentido? pode, portanto, lembrar?

Tenho certeza de que houve uma boa quantidade de discussão sobre isso, e posso imaginar que a escolha foi feita por consistência com a cláusula tryda declaração else:(que eu também preciso procurar) e com o objetivo de não adicionar à lista de Palavras reservadas do Python. Talvez as razões para a escolha elseesclareçam sua função e a tornem mais memorável, mas estou depois de conectar o nome à função, não depois da explicação histórica em si.

As respostas a esta pergunta , que minha pergunta foi brevemente fechada como duplicata, contêm muitas histórias interessantes. Minha pergunta tem um foco diferente (como conectar a semântica específica da elseopção de palavra-chave), mas acho que deve haver um link para essa pergunta em algum lugar.

alexis
fonte
23
Como sobre "se há algo deixado para iterate ... else"
OneCricketeer
4
Eu acho que você pode se lembrar agora depois de escrever esta pergunta :)
Jasper
11
os elsemeios basicamente "se a condição de continuação falhar". Em um loop for tradicional, a condição de continuação é tipicamente i < 42, nesse caso, você pode ver essa parte comoif i < 42; execute the loop body; else; do that other thing
njzk2
1
Isso tudo é verdade, e eu particularmente gosto da resposta do drawoc, mas outra coisa a considerar é que outra é uma palavra-chave disponível que também faz um bom sentido sintaxe. Você pode saber try / except e talvez try / except / finalmente, mas também tem mais - execute esse código se nenhuma exceção acontecer. O que é btw, não é o mesmo que empurrar esse código sob a cláusula try - o tratamento de exceções é melhor usado quando direcionado por pouco. Portanto, embora faça sentido conceitual - de acordo com várias respostas aqui - acho que também é a reutilização de palavras-chave em jogo - execute isso sob certas condições .
JL Peyret
1
@Falanwe, há uma diferença quando o código é encerrado por break. O caso de uso canônico é quando o loop procura por algo e é interrompido quando o encontra. O elseé executado apenas se nada for encontrado.
Alexis

Respostas:

212

(Isso é inspirado na resposta de @Mark Tolonen.)

Uma ifinstrução executa sua elsecláusula se sua condição for avaliada como falsa. Identicamente, um whileloop executa a cláusula else se sua condição for avaliada como falsa.

Esta regra corresponde ao comportamento que você descreveu:

  • Na execução normal, o loop while é executado repetidamente até que a condição seja avaliada como falsa e, portanto, sair naturalmente do loop executa a cláusula else.
  • Ao executar uma breakinstrução, você sai do loop sem avaliar a condição; portanto, a condição não pode ser avaliada como falsa e você nunca executa a cláusula else.
  • Ao executar uma continueinstrução, você avalia a condição novamente e faz exatamente o que faria normalmente no início de uma iteração de loop. Portanto, se a condição for verdadeira, você continua em loop, mas se for falsa, executa a cláusula else.
  • Outros métodos de saída do loop, como return, não avaliam a condição e, portanto, não executam a cláusula else.

foros loops se comportam da mesma maneira. Apenas considere a condição como verdadeira se o iterador tiver mais elementos, ou caso contrário, falso.

drawoc
fonte
8
Esta é uma resposta excelente. Trate seus loops como uma série de declarações elif e o comportamento else irá expor sua lógica natural.
Nomenator
1
Também gosto dessa resposta, mas ela não faz uma analogia com uma série de elifafirmações. Existe uma resposta que sim, e ela tem uma votação líquida.
Alexis8
2
bem, não exatamente, um loop while pode fazer com que a condição atinja False logo antes de breaks; nesse caso, a elseexecução não seria executada, mas a condição é False. Da mesma forma com forloops, pode breakno último elemento.
Tadhg McDonald-Jensen
36

Melhor pensar dessa maneira: o elsebloco sempre será executado se tudo der certo no forbloco anterior , de forma a atingir a exaustão.

Bem neste contexto, significa não exception, não break, não return. Qualquer declaração que controla o seqüestro forfará com que o elsebloco seja ignorado.


Um caso de uso comum é encontrado ao procurar um item em um iterable, para o qual a pesquisa é desativada quando o item é encontrado ou um "not found"sinalizador é levantado / impresso por meio do seguinte elsebloco:

for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")  

A continuenão seqüestra o controle de for, portanto, o controle continuará para o elsedepois que o foresgotamento.

Moses Koledoye
fonte
20
Parece muito bom ... mas você esperaria que uma elsecláusula fosse executada quando as coisas não derem certo, não é? Eu já estou ficando confuso de novo ...
alexis
Eu tenho que discordar de você em "Tecnicamente, não é [semanticamente semelhante a todos os outros else]", pois elseé executado quando nenhuma das condições no loop for avaliada como True, como demonstro na minha resposta
Tadhg McDonald- Jensen
@ TadhgMcDonald-Jensen Você também pode quebrar o loop em um False. Portanto, a questão de como o problema forestá quebrado depende do caso de uso.
Moses Koledoye
É isso mesmo, estou pedindo uma maneira de relacionar de alguma forma o que acontece com o significado em inglês de "else" (que se reflete de fato em outros usos do elsepython). Você fornece um bom resumo intuitivo do que elsefaz, @Moses, mas não de como podemos associar esse comportamento a "else". Se uma palavra-chave diferente fosse usada (por exemplo, nobreakcomo mencionado nesta resposta a uma pergunta relacionada), seria mais fácil entender.
Alexis
1
Realmente não tem nada a ver com "as coisas dando certo". O resto é puramente executado quando a condição if/ é whileavaliada como falsa ou forestá sem itens. breakexiste o loop que contém (após o else). continuevolta e avalia a condição do loop novamente.
Mark Tolonen
31

Quando um ifexecuta um else? Quando sua condição é falsa. É exatamente o mesmo para o while/ else. Portanto, você pode pensar em while/ elsecomo apenas um ifque continua executando sua condição verdadeira até avaliar falso. A breaknão muda isso. Ele simplesmente salta do loop que contém sem avaliação. O elseé executado apenas se a avaliação da condição if/ whilefor falsa.

O foré semelhante, exceto que sua condição falsa está esgotando seu iterador.

continuee breaknão execute else. Essa não é a função deles. A breaksai do loop contendo. A continuevolta para o topo do circuito que contém, em que a condição de ciclo é avaliada. É o ato de avaliar if/ whilepara falso (ou fornão tem mais itens) que executa elsee de nenhuma outra maneira.

Mark Tolonen
fonte
1
O que você diz parece muito sensato, mas agrupar as três condições de terminação "até que [a condição] seja Falsa ou quebre / continue", está errado: Crucialmente, a elsecláusula é executada se o loop for encerrado com continue(ou normalmente), mas não se sairmos com break. Essas sutilezas são o motivo pelo qual estou tentando realmente entender o que elsecapta e o que não capta.
Alexis5
4
@exex sim, eu precisava esclarecer lá. Editado. continue não executa o resto, mas retorna ao topo do loop, que pode ser avaliado como falso.
precisa
24

Isto é o que significa essencialmente:

for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

É uma maneira melhor de escrever esse padrão comum:

found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

A elsecláusula não será executada se houver um returnporque returndeixa a função, como deve. A única exceção àquilo em que você pode estar pensando é finally, cujo objetivo é garantir que ele seja sempre executado.

continuenão tem nada de especial a ver com esse assunto. Isso faz com que a iteração atual do loop termine, o que pode acabar com o loop inteiro e, claramente, nesse caso, o loop não foi encerrado por a break.

try/else É similar:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...
Alex Hall
fonte
20

Se você pensa em seus loops como uma estrutura semelhante a esta (um pouco pseudo-código):

loop:
if condition then

   ... //execute body
   goto loop
else
   ...

pode fazer um pouco mais de sentido. Um loop é essencialmente apenas uma ifdeclaração que é repetida até que a condição seja false. E este é o ponto importante. O loop verifica sua condição e vê que é false, assim, executa o else(como um normal if/else) e, em seguida, o loop é concluído.

Portanto, observe que os else únicos get são executados quando a condição é verificada . Isso significa que, se você sair do corpo do loop no meio da execução com, por exemplo, a returnou a break, como a condição não é verificada novamente, o elsecaso não será executado.

A, continuepor outro lado, interrompe a execução atual e depois volta para verificar a condição do loop novamente, e é por isso que elsepode ser alcançado nesse cenário.

Keiwan
fonte
Gosto muito dessa resposta, mas você pode simplificar: omita o endrótulo e apenas coloque o goto loopinterior do ifcorpo. Talvez até superado colocando o ifmesmo na linha do rótulo, e de repente se parece muito com o original.
Bergi
@ Bergi Sim, acho que isso fica um pouco mais claro, obrigado.
Keiwan
15

Meu momento decisivo com a elsecláusula do loop foi quando eu estava assistindo uma palestra de Raymond Hettinger , que contou uma história sobre como ele achava que deveria ter sido chamada nobreak. Dê uma olhada no código a seguir, o que você acha que ele faria?

for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

O que você acha que faz? Bem, a parte que diz nobreakapenas seria executada se uma breakdeclaração não fosse atingida no loop.

nasser-sh
fonte
8

Normalmente, costumo pensar em uma estrutura de loop assim:

for item in my_sequence:
    if logic(item):
        do_something(item)
        break

Para ser muito parecido com um número variável de if/elifinstruções:

if logic(my_seq[0]):
    do_something(my_seq[0])
elif logic(my_seq[1]):
    do_something(my_seq[1])
elif logic(my_seq[2]):
    do_something(my_seq[2])
....
elif logic(my_seq[-1]):
    do_something(my_seq[-1])

Nesse caso, a elseinstrução no loop for funciona exatamente como a elseinstrução na cadeia de elifs, somente é executada se nenhuma das condições antes de avaliar como True. (ou interrompa a execução com returnou uma exceção) Se meu loop não se encaixa nessa especificação, geralmente opto por não usá-lo for: elsepelo motivo exato pelo qual você postou esta pergunta: não é intuitivo.

Tadhg McDonald-Jensen
fonte
Certo. Mas um loop é executado várias vezes, portanto, não está claro como você deseja aplicar isso a um loop for. Você pode esclarecer?
Alexis5
@alexis Eu refiz minha resposta, acho que está muito mais claro agora.
Tadhg McDonald-Jensen
7

Outros já explicaram a mecânica while/for...elsee a referência da linguagem Python 3 tem a definição autorizada (veja enquanto e para ), mas aqui está o meu mnemônico pessoal, o FWIW. Eu acho que a chave para mim foi dividir isso em duas partes: uma para entender o significado do elseem relação ao condicional do loop e outra para entender o controle do loop.

Acho que é mais fácil começar entendendo while...else:

whilevocê tem mais itens, faz coisas, elsese acabar, faça isso

O for...elsemnemônico é basicamente o mesmo:

forcada item, faça coisas, mas elsese você acabar, faça isso

Nos dois casos, a elsepeça é alcançada apenas quando não houver mais itens a serem processados ​​e o último item foi processado de maneira regular (ou seja, sem breakou return). A continueapenas volta e vê se há mais itens. Meu mnemônico para essas regras se aplica a ambos whilee for:

quando breaking ou returning, não há nada elsea fazer,
e quando digo continue, isso é "voltar ao início" para você

- com "loop back to start", o que significa, obviamente, o início do loop, onde verificamos se há mais itens no iterável; no que diz respeito a ele else, continuerealmente não desempenha nenhum papel.

Fabian Fagerholm
fonte
4
Eu sugeriria que isso pudesse ser aprimorado dizendo que o objetivo usual de um loop for / else é examinar itens até encontrar o que você está procurando e deseja parar ou ficar sem itens. O "else" existe para lidar com a parte "você fica sem itens (sem ter encontrado o que estava procurando)".
supercat
@ supercat: Poderia ser, mas não sei quais são os usos mais comuns por aí. Também elsepode ser usado para fazer algo quando você simplesmente terminar com todos os itens. Os exemplos incluem escrever uma entrada de log, atualizar uma interface do usuário ou sinalizar algum outro processo que você concluiu. Qualquer coisa, mesmo. Além disso, alguns trechos de código têm o final do caso "bem-sucedido" com um breakdentro do loop e elsesão usados ​​para lidar com o caso "error", no qual você não encontrou nenhum item adequado durante a iteração (talvez fosse o que você estava pensando do?).
Fabian Fagerholm 06/06
1
O caso em que eu estava pensando era exatamente o caso em que o caso de sucesso termina com uma "interrupção" e o "else" lida com a falta de sucesso. Se não houver "interrupção" dentro de um loop, o código "else" pode simplesmente seguir o loop como parte do bloco anexo.
Supercat
A menos que você precise distinguir entre o caso em que o loop passou por todos os itens iteráveis ​​sem interrupção (e esse foi um caso de sucesso) e o caso em que não ocorreu. Então você deve colocar o código "finalizando" no elsebloco do loop ou acompanhar o resultado usando outros meios. Eu basicamente concordo, estou apenas dizendo que não sei como as pessoas usam esse recurso e, portanto, gostaria de evitar supor se o elsecenário " lida com caso de sucesso" ou " elselida com caso de sucesso" é mais comum. Mas você tem um bom argumento, então comente com voto positivo!
Fabian Fagerholm 06/06
7

No desenvolvimento orientado a teste (TDD), ao usar o paradigma Transformation Priority Premise , você trata os loops como uma generalização de instruções condicionais.

Essa abordagem combina bem com esta sintaxe, se você considerar apenas declarações simples if/else(não elif):

if cond:
    # 1
else:
    # 2

generaliza para:

while cond:  # <-- generalization
    # 1
else:
    # 2

agradável.

Em outros idiomas, as etapas TDD de um único caso para casos com coleções exigem mais refatoração.


Aqui está um exemplo do blog 8thlight :

No artigo vinculado no blog 8thlight, o kata de quebra automática de linha é considerado: adicionando quebras de linha às seqüências de caracteres (a svariável nos trechos abaixo) para ajustá-las a uma determinada largura (a lengthvariável nos trechos abaixo). Em um ponto, a implementação é a seguinte (Java):

String result = "";
if (s.length() > length) {
    result = s.substring(0, length) + "\n" + s.substring(length);
} else {
    result = s;
}
return result;

e o próximo teste que falhar atualmente é:

@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
    }

Portanto, temos um código que funciona condicionalmente: quando uma condição específica é atendida, uma quebra de linha é adicionada. Queremos melhorar o código para lidar com várias quebras de linha. A solução apresentada no artigo propõe aplicar a transformação (se-> enquanto) , no entanto, o autor faz um comentário que:

Embora os loops não possam ter elsecláusulas, precisamos eliminar o elsecaminho fazendo menos no ifcaminho. Novamente, isso é uma refatoração.

que força a fazer mais alterações no código no contexto de um teste com falha:

String result = "";
while (s.length() > length) {
    result += s.substring(0, length) + "\n";
    s = s.substring(length);
}
result += s;

No TDD, queremos escrever o menos código possível para fazer os testes passarem. Graças à sintaxe do Python, é possível a seguinte transformação:

de:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

para:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s
BartoszKP
fonte
6

A meu ver, else:dispara quando você iterar além do final do loop.

Se você breakou returnou raisevocê não iterate após o final do loop, você parar immeadiately, e, portanto, o else:bloco não será executado. Se você continueainda iterar após o final do loop, pois continue apenas pula para a próxima iteração. Não para o loop.

Winston Ewert
fonte
1
Eu gosto disso, acho que você está interessado em alguma coisa. Ele se relaciona um pouco com a forma como o loop costumava ser implementado nos maus e velhos dias anteriores às palavras-chave do loop. (Ou seja: o cheque foi colocado na parte inferior do loop, com um gototopo em caso de sucesso.) Mas é uma versão mais curta do mais votados resposta ...
alexis
@alexis, subjetivo, mas acho que minha maneira de expressar isso é mais fácil de pensar.
Winston Ewert
na verdade eu concordo. Se apenas porque é mais conciso.
21413 Alexis
4

Pense na elsecláusula como parte da construção do loop; breakquebra completamente a construção do loop e, assim, ignora oelse cláusula

Mas, na verdade, meu mapeamento mental é simplesmente que é a versão 'estruturada' do padrão C / C ++:

  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

Então, quando eu o encontro for...elseou escrevo, em vez de entendê-lo diretamente , eu traduzo-o mentalmente para o entendimento acima do padrão e depois trabalho quais partes da sintaxe do python são mapeadas para quais partes do padrão.

(Coloquei 'estruturado' entre aspas, porque a diferença não é se o código é estruturado ou não estruturado, mas apenas se há palavras-chave e gramática dedicadas à estrutura específica)


fonte
1
Onde está a else? Se você quisesse que o done:rótulo else:fosse proxy ou , acredito que você o tenha exatamente ao contrário.
Alexis
@alexis O código 'else' preencheria o '...' imediatamente antes do done:rótulo. Talvez a correspondência geral seja melhor assim: Python possui a elseconstrução -on-loop para que você possa expressar esse padrão de fluxo de controle sem goto.
Zwol 06/06
Existem outras maneiras de executar esse padrão de fluxo de controle, por exemplo, definindo um sinalizador. É isso que elseevita.
Alexis
2

Se emparelhar elsecom for, pode ser confuso. Não acho que a palavra-chave tenha elsesido uma ótima opção para essa sintaxe, mas se você emparelhar elsecom a ifque contém break, poderá ver que realmente faz sentido. elseé pouco útil se não houver uma ifdeclaração anterior e acredito que é por isso que o designer de sintaxe escolheu a palavra-chave.

Deixe-me demonstrá-lo na linguagem humana.

forcada pessoa em um grupo de suspeitos ifé o criminoso breakda investigação. elserelatar falha.

bombas
fonte
1

Na minha opinião, a chave é considerar o significado de, em continuevez deelse .

As outras palavras-chave que você menciona quebram o loop (saem anormalmente) enquanto continuenão o fazem, apenas ignoram o restante do bloco de código dentro do loop. O fato de que ele pode preceder a terminação do loop é incidental: na verdade, a terminação é feita da maneira normal pela avaliação da expressão condicional do loop.

Então você só precisa se lembrar que a elsecláusula é executada após o encerramento normal do loop.

Bob Sammers
fonte
0
# tested in Python 3.6.4
def buy_fruit(fruits):
    '''I translate the 'else' below into 'if no break' from for loop '''
    for fruit in fruits:
        if 'rotten' in fruit:
            print(f'do not want to buy {fruit}')
            break
    else:  #if no break
        print(f'ready to buy {fruits}')


if __name__ == '__main__':
    a_bag_of_apples = ['golden delicious', 'honeycrisp', 'rotten mcintosh']
    b_bag_of_apples = ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
    buy_fruit(a_bag_of_apples)
    buy_fruit(b_bag_of_apples)

'''
do not want to buy rotten mcintosh
ready to buy ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
'''
Down the Stream
fonte