web-dev-qa-db-fra.com

Comment puis-je donner un sens à la clause `else` des boucles Python?

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.

185
alexis

(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:

  • En exécution normale, la boucle while s'exécute de manière répétée jusqu'à ce que la condition soit évaluée à false. Par conséquent, la sortie naturelle de la boucle exécute la clause else.
  • Lorsque vous exécutez une instruction 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.
  • Lorsque vous exécutez une instruction 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.
  • D'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.

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.

211
drawoc

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.

36
Moses Koledoye

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.

30
Mark Tolonen

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:
    ...
24
Alex Hall

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.

20
Keiwan

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.

14
nasser-sh

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 Elifs; 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.

7

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, mais else 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 breaking ou returning, il n'y a rien que else à faire,
et quand je dis continue, 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.

6
Fabian Fagerholm

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 chemin else en en faisant moins dans le chemin if. 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
6
BartoszKP

À 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. 

5
Winston Ewert

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)

3
Hurkyl

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 suspects if n'importe qui est le criminel break 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.

0
bombs
# 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']
'''
0
Down the Stream

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.

0
Bob Sammers