Comment puis-je comprendre la clause `else` des boucles Python?

191

De nombreux programmeurs Python ignorent probablement que la syntaxe des whileboucles et des forboucles comprend une else:clause facultative :

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

Le corps de la elseclause est un bon endroit pour certains types d'actions de nettoyage et est exécuté à la fin normale de la boucle: c'est-à-dire, quitter la boucle avec returnou breaksauter la elseclause; quitter après une continueexécution. Je le sais uniquement parce que je viens de le chercher (encore une fois), parce que je ne me souviens jamais quand la elseclause est exécutée.

Toujours? Sur «échec» de la boucle, comme son nom l'indique? En cas de résiliation régulière? Même si la boucle est sortie avec return? Je ne peux jamais être entièrement sûr sans le chercher.

Je blâme mon incertitude persistante sur le choix du mot-clé: je trouve elseincroyablement démnémonique pour cette sémantique. Ma question n'est pas "pourquoi ce mot-clé est-il utilisé à cette fin?" (Que je voterais probablement pour fermer, mais seulement après avoir lu les réponses et les commentaires), mais comment puis-je penser au elsemot - clé pour que sa sémantique ait un sens, et je peut donc s'en souvenir?

Je suis sûr qu'il y a eu beaucoup de discussions à ce sujet, et je peux imaginer que le choix a été fait par souci de cohérence avec la clause de la trydéclaration else:(que je dois également consulter), et dans le but de ne pas ajouter à la liste des Les mots réservés de Python. Peut-être que les raisons du choix elseclarifieront sa fonction et la rendront plus mémorable, mais je suis après avoir connecté le nom à la fonction, pas après une explication historique en soi.

Les réponses à cette question , dont ma question a été brièvement clôturée comme un double, contiennent beaucoup d'histoire intéressante. Ma question a un objectif différent (comment relier la sémantique spécifique de elseavec le choix du mot clé), mais je pense qu'il devrait y avoir un lien vers cette question quelque part.

Alexis
la source
23
Que diriez-vous de "s'il reste quelque chose à itérer ... sinon"
OneCricketeer
4
Je pense que vous vous en souvenez maintenant après avoir écrit cette question :)
Jasper
11
cela elsesignifie essentiellement "si la condition de continuation échoue". Dans une boucle for traditionnelle, la condition de continuation est généralement i < 42, dans ce cas, vous pouvez voir cette partie commeif i < 42; execute the loop body; else; do that other thing
njzk2
1
Tout cela est vrai, et j'aime particulièrement la réponse de drawoc, mais une autre chose à considérer est que else est un mot-clé disponible qui a également un bon sens syntaxiquement. Vous savez peut-être essayer / sauf et peut-être essayer / sauf / enfin, mais il en a aussi d' autre - exécutez ce code si aucune exception ne s'est produite. Ce qui est d'ailleurs, ce n'est pas la même chose que de pousser ce code sous la clause try - la gestion des exceptions est mieux utilisée lorsqu'elle est étroitement ciblée. Donc, bien que cela ait un sens conceptuel - selon un certain nombre de réponses ici - je pense que c'est aussi la réutilisation de mots-clés en jeu - exécutez cela sous certaines conditions .
JL Peyret
1
@Falanwe, il y a une différence lorsque le code est sorti par break. Le cas d'utilisation canonique est celui où la boucle recherche quelque chose et se rompt lorsqu'elle le trouve. Le elsen'est exécuté que si rien n'est trouvé.
alexis

Réponses:

213

(Ceci est inspiré de la réponse de @Mark Tolonen.)

Une ifinstruction exécute sa elseclause si sa condition est évaluée à false. De la même manière, une whileboucle exécute la clause else si sa condition est évaluée à false.

Cette règle correspond au comportement que vous avez décrit:

  • Dans une exécution normale, la boucle while s'exécute à plusieurs reprises jusqu'à ce que la condition soit évaluée à false, et par conséquent, quitter naturellement la boucle exécute la clause else.
  • Lorsque vous exécutez une breakinstruction, vous sortez de la boucle sans évaluer la condition, la condition ne peut donc pas être évaluée à false et vous n'exécutez jamais la clause else.
  • Lorsque vous exécutez une continueinstruction, vous évaluez à nouveau la condition et faites exactement ce que vous feriez normalement au début d'une itération de boucle. Donc, si la condition est vraie, vous continuez à boucler, mais si elle est fausse, vous exécutez la clause else.
  • Les autres méthodes de sortie de la boucle, telles que return, n'évaluent pas la condition et n'exécutent donc pas la clause else.

forles boucles se comportent de la même manière. Considérez simplement la condition comme vraie si l'itérateur a plus d'éléments, ou comme fausse dans le cas contraire.

drawoc
la source
8
C'est une excellente réponse. Traitez vos boucles comme une série d'instructions elif et le comportement else exposera sa logique naturelle.
Nomenator
1
J'aime aussi cette réponse, mais elle ne fait pas une analogie avec une série d' elifénoncés. Il y a une réponse qui fait, et il y a un vote positif net.
alexis
2
enfin pas exactement, une boucle while pourrait avoir la condition de répondre à False juste avant qu'elle breaks, auquel cas le elsene s'exécuterait pas mais la condition est False. De même avec des forboucles, il peut breaksur le dernier élément.
Tadhg McDonald-Jensen
36

Mieux vaut le penser de cette façon: le elsebloc sera toujours exécuté si tout va bien dans le forbloc précédent de sorte qu'il atteigne l'épuisement.

Droit dans ce contexte signifie pas exception, non break, non return. Toute instruction qui détourne le contrôle forprovoquera le elsecontournement du bloc.


Un cas d'utilisation courant est trouvé lors de la recherche d'un élément dans un iterable, pour lequel la recherche est soit annulée lorsque l'élément est trouvé, soit un "not found"drapeau est levé / imprimé via le elsebloc suivant :

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

A continuene détourne pas le contrôle de for, donc le contrôle passera au elseaprès que le forest épuisé.

Moïse Koledoye
la source
21
Cela semble très bien ... mais vous vous attendriez alors à ce qu'une elseclause soit exécutée lorsque les choses ne vont pas bien, n'est-ce pas? Je suis déjà de nouveau confus ...
alexis
Je ne suis pas d'accord avec vous sur "Techniquement, ce n'est pas [sémantiquement similaire à tous les autres else]", puisque le elseest exécuté lorsqu'aucune des conditions de la boucle for n'est évaluée à True, comme je le démontre dans ma réponse
Tadhg McDonald- Jensen
@ TadhgMcDonald-Jensen Vous pouvez également interrompre la boucle sur un fichier False. Donc, la question de savoir comment le forest cassé dépend du cas d'utilisation.
Moses Koledoye
C'est vrai, je demande un moyen de relier en quelque sorte ce qui arrive à la signification anglaise de "else" (qui se reflète en effet dans d'autres utilisations de elseen python). Vous fournissez un bon résumé intuitif de ce que elsefait, @Moses, mais pas de la façon dont nous pourrions associer ce comportement à «autre». Si un mot-clé différent était utilisé (par exemple, nobreakcomme mentionné dans cette réponse à une question connexe), il serait plus facile à comprendre.
alexis le
1
Cela n'a vraiment rien à voir avec «les choses vont bien». Le else est purement exécuté lorsque la condition if/ est whileévaluée à false ou forest à court d'éléments. breakexiste la boucle contenant (après le else). continuerevient en arrière et évalue à nouveau la condition de la boucle.
Mark Tolonen
31

Quand un ifexécute- elset-il un ? Lorsque sa condition est fausse. C'est exactement la même chose pour le while/ else. Vous pouvez donc considérer while/ elsecomme un simple ifqui continue d'exécuter sa condition vraie jusqu'à ce qu'il évalue false. A breakne change pas cela. Il saute juste hors de la boucle contenant sans évaluation. Le elsen'est exécuté que si l' évaluation de la condition if/ whileest fausse.

Le forest similaire, sauf que sa condition fausse épuise son itérateur.

continueet breakne pas exécuter else. Ce n'est pas leur fonction. Le breaksort de la boucle contenant. Le continueretourne en haut de la boucle contenant, où la condition de la boucle est évaluée. C'est l'acte d'évaluer if/ whileà faux (ou forqui n'a plus d'éléments) qui s'exécute elseet pas d'autre moyen.

Mark Tolonen
la source
1
Ce que vous dites semble très raisonnable, mais regrouper les trois conditions de terminaison ensemble, "jusqu'à ce que [la condition] soit fausse ou se brise / continue", est faux: de manière cruciale, la elseclause est exécutée si la boucle est quittée avec continue(ou normalement), mais pas si nous sortons avec break. Ces subtilités sont la raison pour laquelle j'essaie de vraiment grok ce qui elsecapture et ce qu'il ne fait pas.
alexis le
4
@alexis oui j'avais besoin de clarifier là. Édité. continue n'exécute pas le else, mais retourne au début de la boucle qui peut alors être évaluée à false.
Mark Tolonen
24

Voici ce que cela signifie essentiellement:

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

C'est une manière plus agréable d'écrire ce modèle commun:

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

La elseclause ne sera pas exécutée s'il y a un returncar returnquitte la fonction comme prévu. La seule exception à ce à laquelle vous pensez peut-être est finally, dont le but est d'être sûr qu'il est toujours exécuté.

continuen'a rien de spécial à voir avec cette question. Cela provoque la fin de l'itération actuelle de la boucle, ce qui peut arriver à terminer la boucle entière, et clairement dans ce cas, la boucle n'a pas été terminée par un break.

try/else est similaire:

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

Si vous pensez à vos boucles comme une structure similaire à celle-ci (un peu pseudo-code):

loop:
if condition then

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

cela pourrait avoir un peu plus de sens. Une boucle est essentiellement juste une ifinstruction qui est répétée jusqu'à ce que la condition le soit false. Et c'est le point important. La boucle vérifie sa condition et voit qu'elle est false, donc exécute le else(comme une normale if/else) et ensuite la boucle est terminée.

Notez donc que le else seul get est exécuté lorsque la condition est vérifiée . Cela signifie que si vous quittez le corps de la boucle en cours d'exécution avec par exemple a returnou a break, puisque la condition n'est pas vérifiée à nouveau, le elsecas ne sera pas exécuté.

A continued'autre part arrête l'exécution en cours, puis revient pour vérifier à nouveau l'état de la boucle, c'est pourquoi le elsepeut être atteint dans ce scénario.

Keiwan
la source
J'aime beaucoup cette réponse, mais vous pouvez simplifier: omettez l' endétiquette et mettez simplement l' goto loopintérieur du ifcorps. Peut-être même devancé en mettant le ifsur la même ligne que l'étiquette, et cela ressemble soudainement à l'original.
Bergi
@Bergi Oui, je pense que cela rend les choses un peu plus claires, merci.
Keiwan
15

Mon coup de tête avec la elseclause de la boucle a été lorsque je regardais une conférence de Raymond Hettinger , qui a raconté une histoire sur la façon dont il pensait qu'elle aurait dû être appelée nobreak. Jetez un œil au code suivant, que pensez-vous qu'il ferait?

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

Que pensez-vous que cela fait? Eh bien, la partie qui dit nobreakne serait exécutée que si une breakinstruction n'était pas touchée dans la boucle.

nasser-sh
la source
8

Habituellement, j'ai tendance à penser à une structure de boucle comme celle-ci:

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

Pour ressembler beaucoup à un nombre variable d' if/elifinstructions:

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

Dans ce cas, l' elseinstruction sur la boucle for fonctionne exactement comme l' elseinstruction sur la chaîne de elifs, elle ne s'exécute que si aucune des conditions avant qu'elle n'évalue à True. (ou interrompre l'exécution avec returnou une exception) Si ma boucle ne correspond pas à cette spécification, je choisis généralement de ne pas utiliser for: elsepour la raison exacte pour laquelle vous avez posé cette question: ce n'est pas intuitif.

Tadhg McDonald-Jensen
la source
Droite. Mais une boucle s'exécute plusieurs fois, il est donc un peu difficile de savoir comment vous voulez l'appliquer à une boucle for. Pouvez-vous clarifier?
alexis le
@alexis J'ai refait ma réponse, je pense que c'est beaucoup plus clair maintenant.
Tadhg McDonald-Jensen
7

D'autres ont déjà expliqué la mécanique de while/for...else, et la référence du langage Python 3 a la définition faisant autorité (voir while et for ), mais voici mon mnémonique personnel, FWIW. Je suppose que la clé pour moi a été de diviser cela en deux parties: une pour comprendre la signification du elseen relation avec la boucle conditionnelle, et une pour comprendre le contrôle de boucle.

Je trouve qu'il est plus facile de commencer par comprendre while...else:

whilevous avez plus d'articles, faites des choses, elsesi vous êtes à court d' articles, faites -le

Le for...elsemnémonique est fondamentalement le même:

forchaque élément, faites des choses, mais elsesi vous êtes à court, faites-le

Dans les deux cas, la elsepièce n'est atteinte que lorsqu'il n'y a plus d'articles à traiter et que le dernier article a été traité de manière régulière (c'est-à-dire non breakou return). A continuerevient simplement et voit s'il y a d'autres éléments. Mon mnémonique pour ces règles s'applique à la fois whileet for:

lors de l' breaking ou returning, il n'y a rien elseà faire,
et quand je dis continue, c'est "boucle de retour pour commencer" pour vous

- avec "loop back to start" signifiant, évidemment, le début de la boucle où l'on vérifie s'il y a plus d'éléments dans l'itérable, donc en ce qui le elseconcerne, continuene joue vraiment aucun rôle.

Fabian Fagerholm
la source
4
Je suggère qu'il pourrait être amélioré en disant que le but habituel d'une boucle for / else est d'examiner les éléments jusqu'à ce que vous ayez trouvé ce que vous cherchez et que vous voulez arrêter , ou que vous soyez à court d'éléments. Le "else" existe pour gérer la partie "vous manquez d'articles (sans avoir trouvé ce que vous cherchiez)".
supercat
@supercat: Peut-être, mais je ne sais pas quelles sont les utilisations les plus courantes. Le elsepeut également être utilisé pour faire quelque chose lorsque vous avez simplement terminé avec tous les éléments. Les exemples incluent l'écriture d'une entrée de journal, la mise à jour d'une interface utilisateur ou la signalisation d'un autre processus que vous avez terminé. Rien? Vraiment. En outre, certains morceaux de code ont la fin de cas "réussie" avec un breakà l'intérieur de la boucle, et le elseest utilisé pour gérer le cas "erreur" où vous n'avez trouvé aucun élément approprié pendant l'itération (peut-être que c'était ce que vous pensiez de?).
Fabian Fagerholm le
1
Le cas auquel je pensais était précisément le cas où le cas réussi se termine par une "pause", et le "else" gère un échec. S'il n'y a pas de "rupture" dans une boucle, le code "else" peut tout aussi bien suivre la boucle dans le cadre du bloc englobant.
supercat le
À moins que vous n'ayez besoin de faire la distinction entre le cas où la boucle a parcouru tous les éléments itérables sans interruption (et c'était un cas réussi) et le cas où ce n'était pas le cas. Ensuite, vous devez mettre le code de "finalisation" dans le elsebloc de la boucle , ou suivre le résultat en utilisant d'autres moyens. Je suis fondamentalement d'accord, je dis simplement que je ne sais pas comment les gens utilisent cette fonctionnalité et par conséquent, j'aimerais éviter de faire des hypothèses sur le fait que le elsescénario " gère les cas réussis" ou " elsegère les cas infructueux" est plus courant. Mais vous avez un bon point, alors commentez!
Fabian Fagerholm le
7

Dans le développement piloté par les tests (TDD), lorsque vous utilisez le paradigme Transformation Priority Premise , vous traitez les boucles comme une généralisation d'instructions conditionnelles.

Cette approche se combine bien avec cette syntaxe, si vous ne considérez que des instructions simples if/else(non elif):

if cond:
    # 1
else:
    # 2

se généralise à:

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

bien.

Dans d'autres langues, les étapes TDD d'un cas unique à des cas avec des collections nécessitent plus de refactorisation.


Voici un exemple du blog 8thlight :

Dans l'article lié au blog 8thlight, le kata Word Wrap est considéré: l'ajout de sauts de ligne aux chaînes (la svariable dans les extraits ci-dessous) pour les faire correspondre à une largeur donnée (la lengthvariable dans les extraits ci-dessous). À un moment donné, l'implémentation se présente comme suit (Java):

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

et le test suivant, qui échoue actuellement, est:

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

Nous avons donc du code qui fonctionne de manière conditionnelle: lorsqu'une condition particulière est remplie, un saut de ligne est ajouté. Nous voulons améliorer le code pour gérer plusieurs sauts de ligne. La solution présentée dans l'article propose d'appliquer la transformation (if-> while) , cependant l'auteur fait un commentaire que:

Alors que les boucles ne peuvent pas avoir de elseclauses, nous devons donc éliminer le elsechemin en faisant moins sur le ifchemin. Encore une fois, il s'agit d'un refactoring.

ce qui oblige à apporter plus de modifications au code dans le contexte d'un test échoué:

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

Dans TDD, nous voulons écrire le moins de code possible pour que les tests réussissent. Grâce à la syntaxe de Python, la transformation suivante est possible:

de:

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

à:

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

La façon dont je le vois, se else:déclenche lorsque vous itérez au-delà de la fin de la boucle.

Si vous breakou returnou raisevous n'itérez pas au-delà de la fin de la boucle, vous vous arrêtez immédiatement et le else:bloc ne fonctionnera donc pas. Si vous continuecontinuez à itérer au-delà de la fin de la boucle, puisque continuer passe simplement à l'itération suivante. Cela n'arrête pas la boucle.

Winston Ewert
la source
1
J'aime ça, je pense que tu es sur quelque chose. Cela correspond un peu à la façon dont la boucle était implémentée dans le mauvais vieux temps avant les mots-clés de boucle. (À savoir: le chèque a été placé en bas de la boucle, avec un gotosuccès en haut.) Mais c'est une version plus courte de la réponse la plus votée ...
alexis
@alexis, subjective, mais je trouve ma façon de l'exprimer plus facile à penser.
Winston Ewert
en fait je suis d'accord. Ne serait-ce que parce que c'est plus corsé.
alexis
4

Pensez à la elseclause comme faisant partie de la construction de boucle; breaksort entièrement de la construction de la boucle et saute ainsi la elseclause.

Mais en réalité, mon mapping mental est simplement que c'est la version `` structurée '' du pattern C / C ++:

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

Ainsi, lorsque je le rencontre for...elseou que je l' écris moi-même, plutôt que de le comprendre directement , je le traduis mentalement dans la compréhension ci-dessus du modèle, puis je détermine quelles parties de la syntaxe python correspondent à quelles parties du modèle.

(Je mets `` structuré '' entre guillemets effrayants car la différence n'est pas de savoir si le code est structuré ou non, mais simplement s'il existe des mots-clés et une grammaire dédiés à la structure particulière)


la source
1
Où est le else? Si vous vouliez que l' done:étiquette soit un proxy ou else:, je crois que vous l'avez exactement à l'envers.
alexis le
@alexis Le code 'else' remplirait le '...' juste avant l' done:étiquette. La correspondance globale est peut-être mieux dite ainsi: Python a la elseconstruction -on-loop afin que vous puissiez exprimer ce modèle de flux de contrôle sans goto.
zwol
Il existe d'autres façons d'exécuter ce modèle de flux de contrôle, par exemple en définissant un indicateur. C'est ce que cela elseévite.
alexis le
2

Si vous vous associez elseavec for, cela pourrait être déroutant. Je ne pense pas que le mot-clé elseétait un excellent choix pour cette syntaxe, mais si vous associez elseavec ifqui contient break, vous pouvez voir que cela a du sens. elseest à peine utile s'il n'y a pas de ifdéclaration précédente et je pense que c'est pourquoi le concepteur de syntaxe a choisi le mot-clé.

Permettez-moi de le démontrer en langage humain.

forchaque personne dans un groupe de suspects ifn'importe qui est le criminel de breakl'enquête. elsesignaler l'échec.

bombes
la source
1

La façon dont j'y pense, la clé est de considérer la signification de continueplutôt que de else.

Les autres mots-clés que vous mentionnez sortent de la boucle (sort anormalement) alors que ce continuen'est pas le cas, il saute simplement le reste du bloc de code à l'intérieur de la boucle. Le fait qu'elle puisse précéder la terminaison de la boucle est accessoire: la terminaison se fait en fait de façon normale par évaluation de l'expression conditionnelle de la boucle.

Ensuite, vous devez simplement vous rappeler que la elseclause est exécutée après la fin de la boucle normale.

Bob Sammers
la source
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']
'''
En bas du ruisseau
la source