De nombreux programmeurs Python ignorent probablement que la syntaxe des boucles while
et for
inclut une clause optionnelle else:
:
for val in iterable:
do_something(val)
else:
clean_up()
Le corps de la clause else
est un bon emplacement pour certains types d'actions de nettoyage et est exécuté à la fin normale de la boucle: c'est-à-dire que quitter la boucle avec return
ou break
ignore la clause else
; quitter après une continue
l'exécute. Je le sais uniquement parce que je ai juste cherché (encore une fois), parce que je ne me souviens jamais quand la clause else
est exécutée.
Toujours? Sur "échec" de la boucle, comme son nom l'indique? Sur résiliation régulière? Même si la boucle est sortie avec return
? Je ne peux jamais être tout à fait sûr sans regarder.
Je blâme mon incertitude persistante sur le choix du mot clé: je trouve else
incroyablement dépourvu de mnémonique pour cette sémantique. Ma question n’est pas "pourquoi ce mot-clé est-il utilisé à cette fin" (que je voterais probablement de fermer, mais seulement après avoir lu les réponses et les commentaires), mais comment puis-je penser au mot-clé else
afin que sa sémantique sens, et je peux donc m'en souvenir?
Je suis sûr que nous en avons beaucoup discuté et je peux imaginer que le choix a été fait pour assurer la cohérence avec la clause else:
de l'instruction try
(que je dois également rechercher), et dans le but de ne pas ajouter de valeur à la liste des mots réservés de Python. Peut-être que les raisons de choisir else
clarifieront sa fonction et la rendront plus mémorable, mais je cherche à connecter 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 une copie, contiennent beaucoup d’histoires intéressantes. Ma question a un objectif différent (comment associer la sémantique spécifique de else
au choix du mot clé), mais j'estime qu'il devrait exister un lien vers cette question quelque part.
(Ceci est inspiré par la réponse de @Mark Tolonen.)
Une instruction if
exécute sa clause else
si sa condition est évaluée à false . Identiquement, une boucle while
exécute la clause else si sa condition est évaluée à false.
Cette règle correspond au comportement que vous avez décrit:
break
, vous quittez la boucle sans évaluer la condition. Par conséquent, la condition ne peut pas être évaluée à false et vous n'exécutez jamais la clause else.continue
, vous évaluez à nouveau la condition et faites exactement ce que vous feriez normalement au début d'une itération de boucle . Ainsi, si la condition est vraie, vous continuez à boucler, mais si elle est fausse, vous exécutez la clause else.return
, n'évaluent pas la condition et n'exécutent donc pas la clause else.Les boucles for
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 faux sinon.
Mieux vaut penser de cette façon: le bloc else
sera toujours exécuté si tout va right dans le précédent bloc for
de manière à atteindre l’épuisement.
Right dans ce contexte signifiera pas exception
, no break
, no return
. Toute déclaration qui détournera le contrôle de for
entraînera le contournement du bloc else
.
Un cas d'utilisation courant est trouvé lors de la recherche d'un élément dans une iterable
, pour laquelle la recherche est soit appelée lorsque l'élément est trouvé, soit un indicateur "not found"
est levé/imprimé via le bloc else
suivant:
for items in basket:
if isinstance(item, Egg):
break
else:
print("No eggs in basket")
Un continue
ne pirate pas le contrôle de for
, le contrôle passera donc à la else
une fois que la for
est épuisée.
Quand une if
exécute-t-elle une else
? Quand sa condition est fausse. Il en va de même pour la variable while
/else
. Ainsi, vous pouvez imaginer que while
/else
soit simplement une if
qui continue à exécuter sa condition vraie jusqu'à ce qu'elle évalue false. Un break
ne change pas cela. Il suffit de sauter de la boucle contenant sans évaluation. La else
n'est exécutée que si evaluation la condition if
/while
est fausse.
La for
est similaire, sauf que sa condition fausse épuise son itérateur.
continue
et break
n'exécutent pas else
. Ce n'est pas leur fonction. La break
quitte la boucle contenant. La continue
retourne au sommet de la boucle contenant, où la condition de la boucle est évaluée. C'est l'acte d'évaluer if
/while
à false (ou for
n'a plus d'éléments) qui exécute else
et pas d'autre moyen.
C'est ce que cela signifie essentiellement:
for/while ...:
if ...:
break
if there was a break:
pass
else:
...
C'est une façon plus agréable d'écrire ce modèle commun:
found = False
for/while ...:
if ...:
found = True
break
if not found:
...
La clause else
ne sera pas exécutée s'il existe une return
car return
quitte la fonction comme il se doit. La seule exception à celle à laquelle vous pouvez penser est finally
, dont le but est d’être sûr qu’elle est toujours exécutée.
continue
n'a rien de spécial à faire avec cette affaire. Cela provoque la fin de l'itération actuelle de la boucle qui peut arriver à mettre fin à la totalité de la boucle, et dans ce cas clairement, la boucle n'a pas été terminée par un break
.
try/else
est similaire:
try:
...
except:
...
if there was an exception:
pass
else:
...
Si vous considérez vos boucles comme une structure semblable à celle-ci (un peu de pseudo-code):
loop:
if condition then
... //execute body
goto loop
else
...
cela pourrait sembler un peu plus logique. Une boucle est essentiellement une instruction if
qui est répétée jusqu'à ce que la condition soit false
. Et c'est le point important. La boucle vérifie sa condition et voit que c'est false
; elle exécute donc la else
(comme un if/else
normal) et la boucle est terminée.
Alors remarquez que la else
ne s’exécute que 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, un return
ou un break
, étant donné que la condition n'est pas vérifiée, le cas else
ne sera pas exécuté.
Par contre, un continue
arrête l'exécution en cours, puis revient à nouveau pour vérifier l'état de la boucle. C'est pourquoi il est possible d'atteindre else
dans ce scénario.
Le moment où je me suis retrouvé avec la clause else
de la boucle était lorsque je regardais une conférence de Raymond Hettinger , qui a raconté une histoire sur la façon dont il pensait que cela aurait dû être appelé nobreak
. Regardez le code suivant, que pensez-vous que cela ferait?
for i in range(10):
if test(i):
break
# ... work with i
nobreak:
print('Loop completed')
Qu'est-ce que tu devines que ça fait? Eh bien, la partie qui dit nobreak
ne sera exécutée que si une instruction break
n'a pas été touchée dans la boucle.
D'habitude, j'ai tendance à penser à une structure en boucle comme celle-ci:
for item in my_sequence:
if logic(item):
do_something(item)
break
Pour ressembler beaucoup à un nombre variable d'instructions if/Elif
:
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'instruction else
de la boucle for fonctionne exactement comme l'instruction else
de la chaîne Elif
s; elle ne s'exécute que si aucune des conditions antérieures à elle n'est évaluée à True. (ou interrompre l'exécution avec return
ou une exception) Si ma boucle ne correspond pas à cette spécification, je choisis généralement de ne pas utiliser for: else
pour la raison exacte pour laquelle vous avez posté cette question: elle n'est pas intuitive.
D'autres ont déjà expliqué les mécanismes de while/for...else
et la référence du langage Python 3 a la définition faisant autorité (voir while et pour ), mais voici mon mnémonique personnelle, FWIW. Je suppose que la clé pour moi a été de diviser cela en deux parties: une pour comprendre le sens de else
par rapport à 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
:
while
vous avez plus d'éléments, faites des choses,else
si vous manquez, faites ceci
Le for...else
mnemonic est fondamentalement le même:
for
chaque élément, faire des choses, maiselse
si vous manquez, faites ceci
Dans les deux cas, la partie else
n'est atteinte que lorsqu'il n'y a plus d'éléments à traiter et que le dernier élément a été traité de manière régulière (c'est-à-dire sans break
ou return
). Un continue
retourne et voit s'il y a d'autres éléments. Mon mnémonique pour ces règles s'applique à la fois à while
et à for
:
quand
break
ing oureturn
ing, il n'y a rien queelse
à faire,
et quand je discontinue
, c'est pour vous "une boucle pour revenir"
- avec "loop back to start" signifiant, évidemment, le début de la boucle où nous vérifions s'il y a plus d'éléments dans l'itérable, de sorte qu'en ce qui concerne else
, continue
ne joue aucun rôle.
Dans développement piloté par les tests _ (TDD), lorsque vous utilisez le paradigme principe de priorité de transformation , vous traitez les boucles comme une généralisation des instructions conditionnelles.
Cette approche se combine bien avec cette syntaxe, si vous ne considérez que des instructions if/else
(no Elif
) simples:
if cond:
# 1
else:
# 2
généralise à:
while cond: # <-- generalization
# 1
else:
# 2
bien.
Dans d'autres langues, les étapes TDD d'un cas à un avec des collections nécessitent davantage de refactoring.
Voici un exemple tiré de blog 8thlight :
Dans l'article lié au blog 8thlight, le kata Word Wrap est pris en compte: l'ajout de sauts de ligne aux chaînes (la variable s
dans les extraits ci-dessous) pour les ajuster à une largeur donnée (la variable length
dans les extraits ci-dessous). À un moment donné, la mise en œuvre 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 prochain test, qui échoue actuellement, est:
@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
}
Nous avons donc un code qui fonctionne conditionnellement: quand 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), mais l'auteur fait un commentaire qui:
Alors que les boucles ne peuvent pas avoir de clauses
else
, nous devons donc éliminer le cheminelse
en en faisant moins dans le cheminif
. Encore une fois, c'est une refactorisation.
qui oblige à apporter plus de modifications au code dans le contexte d'un test qui échoue:
String result = "";
while (s.length() > length) {
result += s.substring(0, length) + "\n";
s = s.substring(length);
}
result += s;
En TDD, nous voulons écrire le moins de code possible pour réussir les tests. 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
À mon avis, else:
se déclenche lorsque vous parcourez la fin de la boucle.
Si vous break
ou return
ou raise
vous n'itérez pas au-delà de la fin de la boucle, vous vous arrêtez immédiatement et le bloc else:
ne s'exécutera pas. Si vous continue
, vous continuez à parcourir la fin de la boucle, car continue continue à la prochaine itération. Cela n'arrête pas la boucle.
Pensez à la clause else
comme faisant partie de la construction de la boucle; break
rompt complètement la construction de boucle et saute ainsi la clause else
.
Mais en réalité, ma cartographie mentale est simplement qu'il s'agit de la version "structurée" du modèle C/C++:
for (...) {
...
if (test) { goto done; }
...
}
...
done:
...
Ainsi, lorsque je rencontre for...else
ou 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 motif, puis je détermine quelles parties de la syntaxe python correspondent à quelles parties du motif.
(Je mets 'structuré' entre guillemets, 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)
Si vous essayez de coupler else
avec for
dans votre esprit, 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 else
à break
, vous pouvez voir qu'il est logique.
Laissez-moi le démontrer en langage humain.
for
chaque personne dans un groupe de suspectsif
n'importe qui est le criminelbreak
l'enquête.else
signaler un échec.
else
est à peine utile s'il n'y avait de toute façon pas break
dans la boucle for
.
# 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 __== '__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']
'''
La façon dont je le pense, la clé est de considérer le sens de continue
plutôt que else
.
Les autres mots clés que vous avez mentionnés sortent de la boucle (sortent de façon anormale), alors que continue
ne le fait pas. Le fait qu'il puisse précéder la terminaison de boucle est fortuit: la terminaison est en fait effectuée normalement, en évaluant l'expression conditionnelle de la boucle.
Ensuite, vous devez simplement vous rappeler que la clause else
est exécutée après la terminaison de boucle normale.