Come posso dare un senso alla clausola `else` dei loop Python?


191

Molti programmatori Python probabilmente non sono consapevoli del fatto che la sintassi dei whileloop e dei forloop include una else:clausola opzionale :

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

Il corpo della elseclausola è un buon posto per alcuni tipi di azioni di pulizia, ed è eseguito al termine normale del ciclo: Cioè, uscendo dal ciclo con returno breaksalta la elseclausola; uscire dopo continueaverlo eseguito. Lo so solo perché l'ho appena cercato (ancora una volta), perché non riesco mai a ricordare quandoelse viene eseguita la clausola.

Sempre? Su "fallimento" del loop, come suggerisce il nome? In caso di risoluzione regolare? Anche se il ciclo viene chiuso con return? Non posso mai essere del tutto sicuro senza cercarlo.

Incolpo la mia incertezza persistente sulla scelta della parola chiave: trovo elseincredibilmente poco mnemonico per questa semantica. La mia domanda non è "perché questa parola chiave viene utilizzata per questo scopo" (che probabilmente voterei per chiudere, anche se solo dopo aver letto le risposte e i commenti), ma come posso pensare alla elseparola chiave in modo che la sua semantica abbia senso, e io può quindi ricordarlo?

Sono sicuro che ci sia stata una buona dose di discussione su questo, e posso immaginare che la scelta sia stata fatta per coerenza con la clausola trydella dichiarazione else:(che devo anche cercare) e con l'obiettivo di non aggiungere all'elenco di Le parole riservate di Python. Forse i motivi della scelta elsechiariranno la sua funzione e la renderanno più memorabile, ma dopo aver collegato il nome alla funzione, non dopo una spiegazione storica in sé.

Le risposte a questa domanda , di cui la mia domanda è stata brevemente chiusa in duplice copia, contengono molte storie retrostanti interessanti. La mia domanda ha un focus diverso (come collegare la semantica specifica della elsescelta della parola chiave), ma ritengo che ci dovrebbe essere un collegamento a questa domanda da qualche parte.


23
Che ne dici di "se c'è ancora qualcosa da ripetere ... altro"
OneCricketeer,

4
Penso che ora te lo ricordi dopo aver scritto questa domanda :)
Jasper,

11
i elsemezzi fondamentalmente, "se la condizione di continuazione fallisce". In un ciclo for tradizionale, la condizione di continuazione è in genere i < 42, nel qual caso è possibile visualizzare quella parte comeif i < 42; execute the loop body; else; do that other thing
njzk2

1
Questo è tutto vero, e mi piace soprattutto la risposta di Drawoc, ma un'altra cosa da considerare è che altro è una parola chiave disponibile che ha anche un buon senso sintattico. Potresti sapere provare / tranne e forse provare / salvo / finalmente, ma ha anche altro - esegui questo codice se non si è verificata alcuna eccezione. Che è btw, non la stessa cosa che inserire questo codice sotto la clausola try - la gestione delle eccezioni è meglio usata quando viene scelta come target. Quindi, sebbene abbia un senso concettuale - come per una serie di risposte qui - penso che anche il riutilizzo delle parole chiave in gioco - esegua questo in determinate condizioni .
JL Peyret,

1
@Falanwe, c'è una differenza quando si esce dal codice break. Il caso d'uso canonico è quando il ciclo cerca qualcosa e si interrompe quando lo trova. L' elseviene eseguito solo se non viene trovato nulla.
alexis,

Risposte:


213

(Questo è ispirato dalla risposta di @Mark Tolonen.)

Un'istruzione ifesegue la sua elseclausola se la sua condizione è falsa. In modo identico, un whileciclo esegue la clausola else se la sua condizione è falsa.

Questa regola corrisponde al comportamento che hai descritto:

  • Nell'esecuzione normale, il ciclo while viene eseguito ripetutamente fino a quando la condizione non viene valutata come falsa, e quindi l'uscita naturale dal ciclo esegue la clausola else.
  • Quando si esegue a break un'istruzione, si esce dal ciclo senza valutare la condizione, quindi la condizione non può essere valutata come falsa e non si esegue mai la clausola else.
  • Quando si esegue a continue un'istruzione, si valuta nuovamente la condizione e si fa esattamente ciò che si farebbe normalmente all'inizio di un'iterazione ciclica. Quindi, se la condizione è vera, continui a eseguire il ciclo, ma se è falsa esegui la clausola else.
  • Altri metodi per uscire dal loop, come return , non valutano la condizione e quindi non eseguono la clausola else.

fori loop si comportano allo stesso modo. Considera la condizione come vera se l'iteratore ha più elementi, o falso altrimenti.


8
Questa è una risposta eccellente. Tratta i tuoi loop come una serie di istruzioni elif e il comportamento else esporrà la sua logica naturale.
Nomenator,

1
Mi piace anche questa risposta, ma non sta disegnando un'analogia con una serie di elifaffermazioni. C'è una risposta che lo fa e ha un voto netto.
alexis,

2
beh, non esattamente, un ciclo while potrebbe avere la condizione che incontra False proprio prima che breaksia, nel qual caso elsenon funzionerebbe ma la condizione è False. Allo stesso modo con i forloop può breaksull'ultimo elemento.
Tadhg McDonald-Jensen,

36

Meglio pensarlo in questo modo: il elseblocco verrà sempre eseguito se tutto va bene nel forblocco precedente in modo tale da raggiungere l'esaurimento.

Proprio in questo contesto significherà no exception, no break, no return. Qualsiasi affermazione che dirotta il controllo forprovocherà il elseblocco del blocco.


Un caso d'uso comune si trova durante la ricerca di un elemento in un iterable, per il quale la ricerca viene annullata quando viene trovato l'elemento o "not found"viene sollevato / stampato un flag tramite il elseblocco seguente :

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

A continuenon dirotta il controllo da for, quindi il controllo procederà al elsetermine fordell'esaurimento.


21
Sembra molto bello ... ma poi ti aspetteresti elseche venga eseguita una clausola quando le cose non vanno bene, vero? Mi sto già confondendo di nuovo ...
Alexis,

Non sono d'accordo con te su "Tecnicamente, non è [semanticamente simile agli altri else]", poiché elseviene eseguito quando nessuna delle condizioni nel ciclo for viene valutata come True, come dimostrerò nella mia risposta
Tadhg McDonald- Jensen,

@ TadhgMcDonald-Jensen Puoi anche interrompere il loop su a False. Quindi la domanda su come forsi rompe dipende dal caso d'uso.
Moses Koledoye,

Esatto, sto chiedendo un modo per mettere in relazione in qualche modo ciò che accade al significato inglese di "else" (che si riflette in effetti in altri usi di elsein Python). Fornisci un buon riepilogo intuitivo di cosa elsefa @Moses, ma non di come potremmo associare questo comportamento a "else". Se viene utilizzata una parola chiave diversa (ad esempio, nobreakcome indicato in questa risposta a una domanda correlata), sarebbe più facile dare un senso.
alexis,

1
Non ha davvero nulla a che fare con "le cose vanno bene". L'altro viene eseguito puramente quando la condizione if/ viene whilevalutata falsa o forè esaurita. breakesiste il ciclo contenente (dopo il else). continuetorna indietro e valuta nuovamente la condizione del loop.
Mark Tolonen,

31

Quando ifesegue un else? Quando la sua condizione è falsa. È esattamente lo stesso per while/ else. Quindi puoi pensare while/ elsecome solo uno ifche continua a funzionare nella sua vera condizione fino a quando non valuta falso. A breaknon cambia questo. Salta fuori dal ciclo di contenimento senza valutazione. L' elseviene eseguita solo se valutare l' if/ whilecondizione è falsa.

L' forè simile, tranne che la sua falsa condizione sta esaurendo il suo iteratore.

continuee breaknon eseguire else. Questa non è la loro funzione. Il breakesce dal ciclo contenente. L' continuerisale all'inizio del ciclo contenente, in cui viene valutata la condizione del ciclo. È l'atto di valutare if/ whilefalsificare (o fornon ha più elementi) che viene eseguito elsee in nessun altro modo.


1
Quello che dici suona molto sensato, ma raggruppando le tre condizioni di terminazione insieme, "fino a quando [la condizione] è Falso o si rompe / continua", è sbagliato: Fondamentalmente, la elseclausola viene eseguita se il ciclo viene chiuso con continue(o normalmente), ma non se usciamo con break. Queste sottigliezze sono il motivo per cui sto cercando di capire davvero cosa elsecattura e cosa no.
alexis,

4
@alexis sì, dovevo chiarire lì. Modificato. continue non esegue l'altro, ma ritorna all'inizio del ciclo che può quindi essere valutato come falso.
Mark Tolonen,

24

Questo è ciò che essenzialmente significa:

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

È un modo migliore di scrivere di questo modello comune:

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

La elseclausola non verrà eseguita se esiste un returnperché returnlascia la funzione, come previsto . L'unica eccezione a ciò a cui potresti pensare è finally, il cui scopo è quello di essere sicuro che sia sempre eseguito.

continuenon ha nulla di speciale a che fare con questa faccenda. Causa la fine dell'iterazione corrente del loop che potrebbe capitare di terminare l'intero loop, e chiaramente in quel caso il loop non è stato terminato da a break.

try/else è simile:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...

20

Se pensi ai tuoi loop come a una struttura simile a questa (in qualche modo pseudo-codice):

loop:
if condition then

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

potrebbe avere un po 'più senso. Un ciclo è essenzialmente solo ifun'istruzione che si ripete fino a quando la condizione lo è false. E questo è il punto importante. Il ciclo controlla le sue condizioni e vede che è false, quindi esegue il else(proprio come un normaleif/else ) e quindi il ciclo è fatto.

Quindi nota che l' else unico get viene eseguito quando viene verificata la condizione . Ciò significa che se si esce dal corpo del loop nel mezzo dell'esecuzione con ad esempio a returno a break, poiché la condizione non viene ricontrollata, il elsecaso non verrà eseguito.

Un continuedall'altro interrompe l'esecuzione corrente e ritornare quindi controlla la condizione del ciclo nuovo, motivo per cui il elsepossono essere raggiunti in questo scenario.


Mi piace praticamente questa risposta, ma puoi semplificare: ometti l' endetichetta e metti l' goto loopinterno del ifcorpo. Forse addirittura superato, mettendo la ifstessa linea dell'etichetta, e all'improvviso assomiglia molto all'originale.
Bergi,

@Bergi Sì, penso che lo renda un po 'più chiaro, grazie.
Keiwan,

15

Il mio momento gotcha con la elseclausola del loop è stato quando stavo guardando un discorso di Raymond Hettinger , che raccontava una storia su come pensava che avrebbe dovuto essere chiamato nobreak. Dai un'occhiata al seguente codice, cosa pensi che farebbe?

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

Cosa indovina? Bene, la parte che dice nobreaksarebbe eseguita solo se breakun'istruzione non fosse stata colpita nel loop.


8

Di solito tendo a pensare a una struttura ad anello come questa:

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

Per essere molto simile a un numero variabile di if/elifaffermazioni:

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])

In questo caso l' elseistruzione sul ciclo for funziona esattamente come l' elseistruzione sulla catena di elifs, viene eseguita solo se nessuna delle condizioni prima di valutare su True. (o interrompere l'esecuzione con returno un'eccezione) Se il mio loop non si adatta a questa specifica di solito scelgo di rinunciare a utilizzare for: elseper il motivo esatto per cui hai postato questa domanda: non è intuitivo.


Destra. Ma un ciclo viene eseguito più volte, quindi non è chiaro come si intende applicare questo a un ciclo continuo. Puoi chiarire?
alexis,

@alexis Ho rifatto la mia risposta, penso che ora sia molto più chiaro.
Tadhg McDonald-Jensen,

7

Altri hanno già spiegato la meccanica di while/for...else, e il riferimento al linguaggio Python 3 ha la definizione autorevole (vedi while e for ), ma ecco il mio mnemonico personale, FWIW. Immagino che la chiave per me sia stata quella di scomporlo in due parti: una per comprendere il significato di elsein relazione al condizionale del loop e una per comprendere il controllo del loop.

Trovo che sia più facile iniziare capendo while...else:

whilehai più oggetti, fai cose, elsese finisci, fallo

Il for...elsemnemonico è sostanzialmente lo stesso:

forogni oggetto, fai cose, ma elsese finisci, fallo

In entrambi i casi, la elseparte viene raggiunta solo quando non ci sono più articoli da elaborare e l'ultimo articolo è stato elaborato in modo regolare (ovvero no breako return). A continuetorna indietro e vede se ci sono altri oggetti. Il mio mnemonico per queste regole si applica a entrambi whilee for:

quando breaking o returning, non c'è niente elseda fare,
e quando dico continue, è "tornare indietro per iniziare" per te

- con "loop back to start" che significa, ovviamente, l'inizio del loop in cui controlliamo se ci sono altri elementi nell'iterabile, quindi, per quanto elseriguarda, continuenon ha alcun ruolo.


4
Suggerirei che potrebbe essere migliorato dicendo che il solito scopo di un ciclo for / else è esaminare gli elementi fino a quando non hai trovato ciò che stai cercando e vuoi fermare , o finisci gli oggetti. L '"altro" esiste per gestire la parte "Sei a corto di oggetti (senza aver trovato quello che cercavi)".
supercat

@supercat: Potrebbe essere, ma non so quali siano gli usi più comuni. L' elsepotrebbe anche essere usato per fare qualcosa quando si sta semplicemente finito con tutti gli elementi. Gli esempi includono la scrittura di una voce di registro, l'aggiornamento di un'interfaccia utente o la segnalazione di altri processi che sono stati eseguiti. Qualunque cosa, davvero. Inoltre, alcune parti di codice hanno la fine "riuscita" del case con un breakciclo interno e elseviene utilizzata per gestire il caso "errore" in cui non è stato trovato alcun elemento adatto durante l'iterazione (forse era quello che stavi pensando di?).
Fabian Fagerholm,

1
Il caso a cui stavo pensando era proprio il caso in cui il caso di successo termina con una "rottura" e "altro" gestisce una mancanza di successo. Se non c'è "interruzione" all'interno di un ciclo, il codice "else" può semplicemente seguire il ciclo come parte del blocco che lo racchiude.
supercat

A meno che non sia necessario distinguere tra il caso in cui il ciclo ha attraversato tutti gli elementi iterabili senza interruzione (e quello è stato un caso di successo) e il caso in cui non è stato eseguito. Quindi devi inserire il codice di "finalizzazione" nel elseblocco del loop o tenere traccia del risultato usando altri mezzi. Sono sostanzialmente d'accordo, sto solo dicendo che non so come le persone usano questa funzione e quindi vorrei evitare di fare ipotesi sul fatto che lo elsescenario " gestisce il caso riuscito" o lo scenario " elsegestisce il caso fallito" sia più comune. Ma hai un buon punto, quindi commenta votato!
Fabian Fagerholm,

7

In Test-driven development (TDD), quando si utilizza il paradigma Transformation Priority Premise , i loop vengono trattati come una generalizzazione delle istruzioni condizionali.

Questo approccio si combina bene con questa sintassi, se si considerano solo istruzioni semplici if/else(no elif):

if cond:
    # 1
else:
    # 2

generalizza a:

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

bene.

In altre lingue, i passaggi TDD da un singolo caso a casi con raccolte richiedono più refactoring.


Ecco un esempio dal blog 8thlight :

Nell'articolo collegato sul blog 8thlight, viene considerato il kata di Word Wrap: aggiunta di interruzioni di riga alle stringhe (la svariabile negli snippet di seguito) per adattarle a una determinata larghezza (la lengthvariabile negli snippet di seguito). Ad un certo punto l'implementazione appare come segue (Java):

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

e il prossimo test, che attualmente fallisce è:

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

Quindi abbiamo un codice che funziona in modo condizionale: quando viene soddisfatta una particolare condizione, viene aggiunta un'interruzione di riga. Vogliamo migliorare il codice per gestire più interruzioni di riga. La soluzione presentata nell'articolo propone di applicare la trasformazione (if-> while) , tuttavia l'autore fa un commento che:

Mentre i loop non possono avere elseclausole, quindi dobbiamo eliminare il elsepercorso facendo meno nel ifpercorso. Ancora una volta, questo è un refactoring.

che impone di apportare ulteriori modifiche al codice nel contesto di un test non riuscito:

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

In TDD vogliamo scrivere meno codice possibile per far passare i test. Grazie alla sintassi di Python è possibile la seguente trasformazione:

a partire dal:

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

per:

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

6

Il modo in cui lo vedo, else:si attiva quando si scorre la fine del ciclo.

Se tu breako returno raisenon iterate oltre la fine del ciclo, vi fermate incerti e quindi il else:blocco non funzionerà. Se continuecontinui a iterare oltre la fine del ciclo, poiché continua salta alla successiva iterazione. Non ferma il ciclo.


1
Mi piace, penso che tu abbia voglia di qualcosa. Si lega un po 'con il modo in cui il looping veniva implementato nei vecchi tempi delle parole chiave loop. (Vale a dire: il segno di spunta è stato posto in fondo al ciclo, con un gotosuccesso in cima.) Ma è una versione più breve della risposta più votata ...
alexis,

@alexis, soggettivo, ma trovo il mio modo di esprimere più facile pensarci.
Winston Ewert,

in realtà sono d'accordo. Se non altro perché è più pigro.
alexis,

4

Pensa alla elseclausola come parte del costrutto del ciclo; breakesce completamente dal costrutto loop e quindi salta ilelse clausola.

Ma davvero, la mia mappatura mentale è semplicemente che è la versione "strutturata" del modello C / C ++ del modello:

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

Quindi quando lo incontro for...elseo lo scrivo da solo, piuttosto che capirlo direttamente , lo traduco mentalmente nella comprensione sopra del modello e quindi capisco quali parti della sintassi del pitone mappano a quali parti del motivo.

(Metto "strutturato" tra virgolette perché la differenza non è se il codice è strutturato o non strutturato, ma semplicemente se ci sono parole chiave e grammatica dedicate alla particolare struttura)


1
Dov'è il else? Se intendevi che l' done:etichetta fosse proxy o else:, credo che tu l'abbia esattamente indietro.
alexis,

@alexis Il codice 'altro' avrebbe compilare il '...' immediatamente prima della done:etichetta. La corrispondenza generale è, forse, meglio detta così: Python ha il elsecostrutto -on-loop in modo da poter esprimere questo schema di flusso di controllo senza goto.
zwol,

Esistono altri modi per eseguire questo modello di flusso di controllo, ad esempio impostando un flag. Questo è ciò che elseevita.
alexis,

2

Se ti accoppi elsecon for, potrebbe essere fonte di confusione. Non credo che la parola chiave sia elsestata un'ottima scelta per questa sintassi, ma se si accoppia elsecon ifquale contiene break, si può vedere che ha effettivamente senso. elseè a malapena utile se non ci sono precedentiif dichiarazioni e credo che sia per questo che il progettista della sintassi abbia scelto la parola chiave.

Lascia che te lo dimostri in linguaggio umano.

forogni persona in un gruppo di sospetti ifchiunque è il criminale breakdell'indagine. elsesegnalare un errore.


1

Per come la penso, la chiave è considerare il significato continuepiuttosto cheelse .

Le altre parole chiave che menzioni escono dal ciclo (escono in modo anomalo) mentre continue non lo fanno, salta semplicemente il resto del blocco di codice all'interno del ciclo. Il fatto che possa precedere la terminazione del loop è casuale: la terminazione viene effettivamente eseguita normalmente attraverso la valutazione dell'espressione condizionale del loop.

Quindi devi solo ricordare che la elseclausola viene eseguita dopo la normale terminazione del loop.


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']
'''
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.