web-dev-qa-db-fra.com

Pourquoi python utilise-t-il 'else' après les boucles for et while?

Je comprends comment cette construction fonctionne:

for i in range(10):
    print(i)

    if i == 9:
        print("Too big - I'm giving up!")
        break;
else:
    print("Completed successfully")

Mais je ne comprends pas pourquoi else est utilisé comme mot clé ici, car il suggère que le code en question ne fonctionne que si le bloc for ne se termine pas, ce qui est le contraire de ce qu'il fait! Quelle que soit ma façon de penser, mon cerveau ne peut pas passer de l'instruction for au bloc else de manière transparente. continue ou continuewith aurait plus de sens (et j'essaie de m'entraîner à le lire comme tel).

Je me demande comment les codeurs Python ont lu cette construction dans leur tête (ou à voix haute, si vous préférez). Peut-être me manque quelque chose qui rendrait de tels blocs de code plus facilement déchiffrables?

330
Kent Boogaart

C'est une construction étrange même pour les codeurs aguerris de Python. Utilisé en conjonction avec des boucles for, cela signifie fondamentalement "trouve un élément dans l'itérable, sinon si rien n'a été trouvé ...". Un péché:

found_obj = None
for obj in objects:
    if obj.key == search_key:
        found_obj = obj
        break
else:
    print('No object found.')

Mais chaque fois que vous voyez cette construction, une meilleure alternative consiste à encapsuler la recherche dans une fonction:

def find_obj(search_key):
    for obj in objects:
        if obj.key == search_key:
            return obj

Ou utilisez une liste de compréhension:

matching_objs = [o for o in objects if o.key == search_key]
if matching_objs:
    print('Found {}'.format(matching_objs[0]))
else:
    print('No object found.')

Il n’est pas sémantiquement équivalent aux deux autres versions, mais fonctionne assez bien dans du code critique non performant, qu’il importe ou pas d’itérer la liste entière. D'autres peuvent ne pas être d'accord, mais personnellement, je ne voudrais jamais utiliser les blocs for-else ou while-else dans le code de production. 

Voir aussi [Python-ideas] Résumé de for ... else threads

202
Björn Lindqvist

Une construction commune consiste à exécuter une boucle jusqu'à ce que quelque chose soit trouvé, puis à sortir de la boucle. Le problème est que si je sors de la boucle ou que la boucle se termine, je dois déterminer quel cas s'est produit. Une méthode consiste à créer un indicateur ou une variable de stockage qui me permettra de faire un deuxième test pour voir comment la boucle a été sortie.

Par exemple, supposons que je doive parcourir une liste et traiter chaque élément jusqu'à ce qu'un élément indicateur soit trouvé, puis arrêter le traitement. Si l'élément de drapeau est manquant, une exception doit être déclenchée.

En utilisant la construction for...else de Python que vous avez

for i in mylist:
    if i == theflag:
        break
    process(i)
else:
    raise ValueError("List argument missing terminal flag.")

Comparez ceci à une méthode qui n'utilise pas ce sucre syntaxique:

flagfound = False
for i in mylist:
    if i == theflag:
        flagfound = True
        break
    process(i)

if not flagfound:
    raise ValueError("List argument missing terminal flag.")

Dans le premier cas, la variable raise est étroitement liée à la boucle for avec laquelle elle fonctionne. Dans le second cas, la liaison n’est pas aussi forte et des erreurs peuvent être introduites pendant la maintenance.

402
Lance Helsten

Une excellente présentation de Raymond Hettinger, intitulée Transforming Code in Beautiful, Idiomatic Python , dans laquelle il aborde brièvement l’histoire de la construction for ... else. La section correspondante est "Distinguer plusieurs points de sortie dans des boucles" commençant à 15h50 et se poursuivant pendant environ trois minutes. Voici les points forts:

  • Donald Knuth a conçu la construction for ... else en remplacement de certains cas d'utilisation GOTO;
  • Réutiliser le mot clé else était logique car "c'est ce que Knuth a utilisé et les gens savaient qu'à ce moment-là, toutes les [déclarations for] avaient incorporé un if et un GOTO dessous, et ils s'attendaient à un else;"
  • Avec le recul, cela aurait dû s'appeler "no break" (ou éventuellement "nobreak"), et ce ne serait alors pas déroutant. *

Donc, si la question est: "Pourquoi ne changent-ils pas ce mot-clé?" then Cat Plus Plus a probablement donné la réponse la plus précise - à ce stade, il serait trop destructeur pour le code existant pour être pratique. Mais si la question que vous vous posez réellement est de savoir pourquoi else a été réutilisé, eh bien, apparemment, cela semblait être une bonne idée à l'époque.

Personnellement, j'aime bien le fait de commenter # no break en ligne chaque fois que la variable else peut être confondue, en un coup d'œil, avec celle qui appartient à la boucle. C'est raisonnablement clair et concis. Cette option est brièvement mentionnée dans le résumé que Bjorn a lié à la fin de sa réponse:

Pour être complet, je devrais mentionner cela avec un léger changement dans syntaxe, les programmeurs qui veulent cette syntaxe peuvent l’avoir maintenant:

for item in sequence:
    process(item)
else:  # no break
    suite

* Citation bonus de cette partie de la vidéo: "Comme si nous appelions lambda makefunction, personne ne demanderait" Que fait lambda? "

134
Air

Parce qu'ils ne voulaient pas introduire un nouveau mot clé dans la langue. Chacun vole un identifiant et provoque des problèmes de compatibilité ascendante, il s’agit donc généralement d’un dernier recours.

30
Cat Plus Plus

Le moyen le plus simple que j'ai trouvé pour «obtenir» ce que le/pour a fait et, plus important encore, quand l'utiliser, était de se concentrer sur l'endroit où la déclaration de rupture saute à. La construction For/else est un seul bloc. La pause saute hors du bloc et saute donc «par-dessus» la clause else. Si le contenu de la clause else suivait simplement la clause for, il ne serait jamais sauté de dessus, et la logique équivalente devrait donc être fournie en la plaçant dans un if. Cela a déjà été dit, mais pas tout à fait comme cela, cela pourrait aider quelqu'un d'autre. Essayez d'exécuter le fragment de code suivant. Je suis de tout coeur en faveur du commentaire «no break» pour plus de clarté.

for a in range(3):
    print(a)
    if a==4: # change value to force break or not
        break
else: #no break  +10 for whoever thought of this decoration
    print('for completed OK')

print('statement after for loop')
14
Neil_UK

Je l'ai lu quelque chose comme:

Si toujours sur les conditions pour exécuter la boucle, faire des choses, sinon faire autre chose.

12
pcalcao

Je pense que la documentation a une excellente explication de else, continue

[...] il est exécuté lorsque la boucle se termine par l'épuisement de la liste (avec for) ou lorsque la condition devient fausse (avec while), mais pas lorsque la boucle est terminée par une instruction break. "

Source: Python 2 docs: Tutoriel sur les flux de contrôle

12
Ayan

Pour faire simple, vous pouvez le penser comme ça;

  • S'il rencontre la commande break dans la boucle for, la partie else ne sera pas appelée.
  • S'il ne rencontre pas la commande break dans la boucle for, la partie else sera appelée.

En d'autres termes, si l'itération de boucle for n'est pas "rompue" avec break, la partie else sera appelée.

C'est si simple. 

11
Ad Infinitum

Puisque la partie technique a été assez bien répondue, mon commentaire est juste en relation avec le confusion qui produit ce mot clé Recyclé.

Étant un langage de programmation très éloquent, l'utilisation abusive d'un mot clé est plus notoire. Le mot clé else décrit parfaitement une partie du flux d'un arbre de décision: "si vous ne pouvez pas faire cela, (sinon) faites-le". C'est impliqué dans notre propre langue.

Au lieu de cela, l’utilisation de ce mot clé avec les instructions while et for crée la confusion. La raison pour laquelle notre carrière de programmeurs nous a appris que la déclaration else réside dans un arbre de décision; sa portée logique , un wrapper qui conditionally renvoie un chemin à suivre. Pendant ce temps, les instructions de boucle ont un objectif explicite figuratif d'atteindre quelque chose. L'objectif est atteint après des itérations continues d'un processus. 

if / elseindique un chemin à suivre. Les boucles suivent un chemin jusqu’à ce que "l’objectif" soit terminé.

Le problème est que else est un mot qui définit clairement la dernière option d'une condition. Les sémantiques du mot sont tous deux partagés par Python et le langage humain. Mais l’autre mot du langage humain n’est jamais utilisé pour indiquer les actions que quelqu'un ou quelque chose entreprendra une fois que quelque chose est terminé. Il sera utilisé si, au cours de son achèvement, un problème se pose (plutôt comme une déclaration break).

A la fin, le mot clé restera en Python. Il est clair que c’était une erreur, plus claire lorsque chaque programmeur essaie de créer une histoire pour en comprendre l’utilisation, comme un dispositif mnémonique. J'aurais aimé s'ils choisissaient plutôt le mot clé then. Je crois que ce mot-clé s’intègre parfaitement dans ce flux itératif, le payoff après la boucle.

Cela ressemble à la situation que certains enfants ont après avoir suivi chaque étape du montage d'un jouet: EtALORSquel papa?

6
3rdWorldCitizen

Je l'ai lu ainsi: "Lorsque la iterable est complètement épuisée et que l'exécution est sur le point de passer à l'instruction suivante après avoir terminé la for, la clause else sera exécutée." Ainsi, lorsque l'itération est interrompue par break, cela ne sera pas exécuté.

5
0xc0de

Je suis d'accord, cela ressemble plus à un 'Elif pas [condition (s) levant la pause]'.

Je sais que c'est un vieux fil, mais je suis en train de regarder la même question en ce moment, et je ne suis pas sûr que quiconque ait capturé la réponse à cette question telle que je la comprends.

Pour moi, il y a trois façons de "lire" la else dans les instructions For... else ou While... else, qui sont toutes équivalentes, sont les suivantes:

  1. else==if the loop completes normally (without a break or error)
  2. else==if the loop does not encounter a break
  3. else==else not (condition raising break) (vraisemblablement, une telle condition existe, sinon vous n'auriez pas de boucle)

Donc, essentiellement, le "else" dans une boucle est vraiment un "Elif ..." où '...' est (1) pas de pause, ce qui équivaut à (2) PAS [condition (s) levant la pause ].

Je pense que la clé est que la else est inutile sans la "pause", donc un for...else comprend:

for:
    do stuff
    conditional break # implied by else
else not break:
    do more stuff

Ainsi, les éléments essentiels d’une boucle for...else sont les suivants, et vous les liriez plus simplement en anglais comme suit:

for:
    do stuff
    condition:
        break
else: # read as "else not break" or "else not condition"
    do more stuff

Comme l'ont dit les autres affiches, une pause est généralement levée lorsque vous êtes en mesure de localiser ce que votre boucle recherche, ainsi le else: devient "que faire si l'élément cible n'est pas localisé".

Exemple

Vous pouvez également utiliser la gestion des exceptions, les pauses et les boucles for ensemble.

for x in range(0,3):
    print("x: {}".format(x))
    if x == 2:
        try:
            raise AssertionError("ASSERTION ERROR: x is {}".format(x))
        except:
            print(AssertionError("ASSERTION ERROR: x is {}".format(x)))
            break
else:
    print("X loop complete without error")

Résultat

x: 0
x: 1
x: 2
ASSERTION ERROR: x is 2
----------
# loop not completed (hit break), so else didn't run

Exemple

Exemple simple avec une pause frappée.

for y in range(0,3):
    print("y: {}".format(y))
    if y == 2: # will be executed
        print("BREAK: y is {}\n----------".format(y))
        break
else: # not executed because break is hit
    print("y_loop completed without break----------\n")

Résultat

y: 0
y: 1
y: 2
BREAK: y is 2
----------
# loop not completed (hit break), so else didn't run

Exemple

Exemple simple où il n'y a pas de pause, pas de condition donnant lieu à une pause et aucune erreur n'est rencontrée.

for z in range(0,3):
     print("z: {}".format(z))
     if z == 4: # will not be executed
         print("BREAK: z is {}\n".format(y))
         break
     if z == 4: # will not be executed
         raise AssertionError("ASSERTION ERROR: x is {}".format(x))
else:
     print("z_loop complete without break or error\n----------\n")

Résultat

z: 0
z: 1
z: 2
z_loop complete without break or error
----------
4
NotAnAmbiTurner

Les codes du bloc d'instructions else seront exécutés lorsque la boucle for ne serait pas cassée.

for x in xrange(1,5):
    if x == 5:
        print 'find 5'
        break
else:
    print 'can not find 5!'
#can not find 5!

À partir de docs: Interrompre et continuer les déclarations, ou encore Clauses on Loops

Les instructions de boucle peuvent avoir une clause else; il est exécuté lorsque la boucle se termine par l'épuisement de la liste (avec for) ou lorsque la condition devient fausse (avec while), mais pas lorsque la boucle est terminée par une instruction break. Ceci est illustré par la boucle suivante, qui recherche des nombres premiers:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Oui, c'est le code correct. Regardez bien: la clause else appartient à la boucle for, pas à l'instruction if.)

Lorsqu'elle est utilisée avec une boucle, la clause else a plus de points en commun avec la clause else d'une instruction try que celle des instructions if: la clause else d'une instruction try s'exécute lorsqu'aucune exception ne se produit, et la clause else d'une boucle s'exécute sans interruption . Pour plus d'informations sur l'instruction try et les exceptions, voir Gestion des exceptions.

L'instruction continue, également empruntée à C, continue à la prochaine itération de la boucle:

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Found an even number", num)
...         continue
...     print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9
3
GoingMyWay

Le mot clé else peut être déroutant ici, et comme beaucoup de personnes l’ont souligné, un élément comme nobreak, notbreak est plus approprié.

Pour comprendre logiquement for ... else ..., comparez-le avec try...except...else et non pas if...else..., la plupart des programmeurs python connaissent bien le code suivant:

try:
    do_something()
except:
    print("Error happened.") # The try block threw an exception
else:
    print("Everything is find.") # The try block does things just find.

De même, considérez break comme un type spécial de Exception:

for x in iterable:
    do_something(x)
except break:
    pass # Implied by Python's loop semantics
else:
    print('no break encountered')  # No break statement was encountered

La différence est python implique except break et vous ne pouvez pas l'écrire, il devient donc:

for x in iterable:
    do_something(x)
else:
    print('no break encountered')  # No break statement was encountered

Oui, je sais que cette comparaison peut être difficile et fastidieuse, mais elle clarifie la confusion.

3
cizixs

Vous pourriez penser à cela, comme else comme dans le reste des choses, ou les autres choses, qui n'ont pas été faites dans la boucle.

2
jamylak

Voici une façon de penser à ce sujet que je n'ai vu personne mentionner plus haut:

Tout d’abord, rappelez-vous que les boucles for-loops ne sont fondamentalement que du sucre syntaxique. Par exemple, la boucle

for item in sequence:
    do_something(item)

peut être réécrit (approximativement) comme

item = None
while sequence.hasnext():
    item = sequence.next()
    do_something(item)

Deuxièmement, rappelez-vous que les boucles while sont simplement répétées si des blocs! Vous pouvez toujours lire une boucle while comme "si cette condition est vraie, exécutez le corps, puis revenez et vérifiez à nouveau".

Ainsi, while/else est parfaitement logique: c'est exactement la même structure que if/else, avec la fonctionnalité supplémentaire de bouclage jusqu'à ce que la condition devienne fausse au lieu de simplement vérifier la condition une fois.

Et puis pour/else est tout à fait logique aussi: parce que toutes les boucles for-loop ne sont que du sucre syntaxique au-dessus de while-loops, il vous suffit de déterminer quel est le conditionnel implicite sous-jacent de while-boucle, et le reste correspond à la condition devient False.

2
Aaron Gable

En plus de la recherche, voici un autre cas d'utilisation idiomatique. Supposons que vous souhaitiez attendre qu'une condition soit vraie, par exemple. un port à ouvrir sur un serveur distant, avec un délai d'attente. Ensuite, vous pouvez utiliser une construction while...else comme ceci:

import socket
import time

sock = socket.socket()
timeout = time.time() + 15
while time.time() < timeout:
    if sock.connect_ex(('127.0.0.1', 80)) is 0:
        print('Port is open now!')
        break
    print('Still waiting...')
else:
    raise TimeoutError()
1

Python utilise une autre boucle après et tant, de sorte que si rien ne s'applique à la boucle, quelque chose d'autre se produit. Par exemple:

test = 3
while test == 4:
     print("Hello")
else:
     print("Hi")

La sortie serait "Salut" maintes et maintes fois (si je suis correct).

0
Mrmongoose64

Supposons que nous ayons une fonction

def broken(x) : return False if x==5 else True

Ce qui signifie que seulement 5 n'est pas cassé. Maintenant dans le cas où broken n'est jamais évalué avec 5: -

for x in range(4):
    if not broken(x) : break
else:
    print("Everything broken... Doom is upon us")

Donnera la sortie: -

Everything broken... Doom is upon us

Où quand broken est évalué avec 5: -

for x in range(6):
    if not broken(x) : break
else:
    print("Everything broken... Doom is upon us")

Cela n'imprimera rien. Ainsi, dire indirectement qu'il y a au moins quelque chose qui n'est pas cassé.

Cependant, au cas où vous voudriez tricher et ignorer quelque chose que vous avez trouvé était cassé. C'est-à-dire, continuez la boucle même si vous avez trouvé 5 comme cassé, sinon l'instruction sera toujours imprimée. C'est :-

for x in range(6):
    if not broken(x) : continue
else:
    print("Everything broken... Doom is upon us")

Va imprimer 

Everything broken... Doom is upon us

J'espère que cela dissipe la confusion au lieu d'en créer un nouveau :-)

0
satyakam shashwat

J'essayais juste de redonner un sens à moi-même. J'ai trouvé que ce qui suit aide! 

• Pensez à la else comme étant associée àifà l'intérieur de la boucle (au lieu de la for) - si la condition est remplie, cassez la boucle, sinon faites ceci - sauf qu'il s'agit d'une else associée à plusieurs ifs!
• Si aucune ifs n'a été satisfaite du tout, faites la else.
• Les multiples ifs peuvent également être considérés comme if-Elifs!

0
Germaine Goh
for i in range(3):
    print(i)

    if i == 2:
        print("Too big - I'm giving up!")
        break;
else:
    print("Completed successfully")

"else" ici est incroyablement simple, il suffit de dire 

1, "si for clause est terminé"

for i in range(3):
    print(i)

    if i == 2:
        print("Too big - I'm giving up!")
        break;
if "for clause is completed":
    print("Completed successfully")

C'est maniable d'écrire des instructions aussi longues que "for clause is complete", donc ils introduisent "else".

else Voici un si dans sa nature.

2, cependant, que diriez-vous de for clause is not run at all

In [331]: for i in range(0):
     ...:     print(i)
     ...: 
     ...:     if i == 9:
     ...:         print("Too big - I'm giving up!")
     ...:         break
     ...: else:
     ...:     print("Completed successfully")
     ...:     
Completed successfully

Donc, il est complètement énoncé est la combinaison logique:

if "for clause is completed" or "not run at all":
     do else stuff

ou le dire de cette façon:

if "for clause is not partially run":
    do else stuff

ou de cette façon:

if "for clause not encounter a break":
    do else stuff
0
JawSaw

Les bonnes réponses sont:

  • ceci qui explique l'histoire, et
  • this donne le droit de faciliter votre traduction/compréhension.

Ma note ici vient de ce que Donald Knuth a dit une fois (pardon, impossible de trouver une référence) qu'il existe une construction où while-else est indiscernable de if-else, à savoir (en Python):

x = 2
while x > 3:
    print("foo")
    break
else:
    print("boo")

a le même flux (à l'exclusion des différences de niveau bas) que:

x = 2
if x > 3:
    print("foo")
else:
    print("boo")

Le fait est que if-else peut être considéré comme un sucre syntaxique pour while-else qui présente une rupture implicite à la fin de son bloc if. L'implication contraire, cette boucle while est l'extension de if, car il s'agit simplement d'une vérification conditionnelle répétée. Cependant, il est insuffisant lorsque vous considérez si-else, car cela signifierait que else block in while-else serait exécuté chaque fois lorsque la condition est fausse.

Pour faciliter votre compréhension, pensez de cette façon:

Sans la boucle break, return, etc., la boucle ne se termine que lorsque la condition n'est plus vraie (dans le cas for, vous devez considérer les boucles for de style C ou les traduire en while) et le bloc else lorsque la condition est false.

Une autre note:

Une boucle intérieure prématurée break, return, etc. empêche la condition de devenir fausse car l'exécution saute hors de la boucle alors que la condition était vraie et ne reviendrait jamais pour la vérifier à nouveau.

0
WloHu