649 votes

Comment sortir des boucles multiples ?

Etant donné le code suivant (qui ne fonctionne pas) :

while True:
    # Snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok.lower() == "y": break 2 # This doesn't work :(
        if ok.lower() == "n": break

    # Do more processing with menus and stuff

Existe-t-il un moyen de faire fonctionner ce système ? Ou dois-je faire une vérification pour sortir de la boucle d'entrée, puis une autre vérification, plus limitée, dans la boucle extérieure pour sortir de l'ensemble si l'utilisateur est satisfait ?

161 votes

Pourquoi Python ne propose-t-il pas simplement "break(n)", où n est le nombre de niveaux que l'on veut quitter.

9 votes

Le C++ est bien ici avec goto si vous êtes niché au cœur d'un grand nombre de boucles

0 votes

@Nathan See Pourquoi python ne permet pas nativement un mot clé comme goto pour sortir de n boucles une très belle explication de la part de nathan

671voto

Robert Rossney Points 43767

Mon premier réflexe serait de refondre la boucle imbriquée en une fonction et d'utiliser la fonction return d'éclater.

3 votes

C'est une autre idée que j'ai eue, car une fonction get_input_yn() serait utile ailleurs aussi, j'en suis sûr.

147 votes

Je suis d'accord dans ce cas précis, mais dans le cas général "j'ai des boucles imbriquées, que dois-je faire", le remaniement n'a peut-être pas de sens.

0 votes

L'utilisation d'une exception peut être plus facile lorsque vous devez faire un yield au lieu d'utiliser un return, mais vous devriez probablement utiliser itertools.islice() dans un tel cas.

412voto

yak Points 241

Voici une autre approche qui est courte. L'inconvénient est que vous ne pouvez casser que la boucle extérieure, mais parfois c'est exactement ce que vous voulez.

for a in xrange(10):
    for b in xrange(20):
        if something(a, b):
            # Break the inner loop...
            break
    else:
        # Continue if the inner loop wasn't broken.
        continue
    # Inner loop was broken, break the outer.
    break

Ceci utilise la construction for / else expliquée à l'article : Pourquoi Python utilise-t-il "else" après les boucles for et while ?

Principaux enseignements : il n'y a qu'à semble comme si la boucle extérieure s'interrompait toujours. Mais si la boucle interne n'est pas interrompue, la boucle externe ne le sera pas non plus.

En continue est la magie de la déclaration. Elle se trouve dans la clause for-else. Par définition qui se produit s'il n'y a pas de rupture intérieure. Dans ce cas continue contourne parfaitement la rupture extérieure.

0 votes

@RishitBansal Bien qu'il s'agisse d'une coupure profonde : La boucle extérieure a de l'importance parce que la condition de rupture intérieure something(a, b) dépend de a aussi. La boucle extérieure peut durer aussi longtemps que something(a, b) n'est pas True .

2 votes

Ceci provient d'une vidéo de Raymond Hettinger, youtu.be/OSGv2VnC0go?t=971 Si vous avez l'habitude de lire les instructions "else" attachées aux boucles "for" comme "no_break", cela devient plus facile à comprendre.

8 votes

C'est astucieux :-) Mais ce n'est pas simple. Franchement, je ne suis pas convaincu par les arguments en faveur du maintien de la pause étiquetée ou de break(n) en Python. Les solutions de contournement ajoutent de la complexité.

171voto

John Fouhy Points 14700

PEP 3136 propose une pause/continuation étiquetée. Guido l'a rejeté car "il est très rare qu'un code soit si compliqué qu'il nécessite cette fonctionnalité". Le PEP mentionne cependant quelques solutions de contournement (comme la technique de l'exception), tandis que Guido estime qu'un remaniement pour utiliser le retour sera plus simple dans la plupart des cas.

92 votes

Cependant, refactor/ return est généralement la meilleure solution, mais j'ai vu plusieurs cas où une simple phrase concise ' break 2 serait tellement plus logique. Aussi, refactoriser/ return ne fonctionne pas de la même manière pour continue . Dans ces cas, il serait plus facile de suivre les instructions numériques break et continue et moins encombrant que de refaire une fonction minuscule, de soulever des exceptions ou de suivre une logique alambiquée impliquant la mise en place d'un drapeau de break à chaque niveau d'imbrication. Il est dommage que Guido l'ait rejeté.

13 votes

break; break serait bien.

2 votes

Je suis d'accord avec Guido. Le zen de Python dit "Flat is better than nested". Il y a rarement des situations où vous ne pouvez pas éviter d'avoir trois boucles imbriquées ou plus.

146voto

S.Lott Points 207588

Tout d'abord, la logique ordinaire est utile.

Si, pour une raison ou une autre, les conditions de résiliation ne peuvent être définies, les exceptions constituent un plan de repli.

class GetOutOfLoop( Exception ):
    pass

try:
    done= False
    while not done:
        isok= False
        while not (done or isok):
            ok = get_input("Is this ok? (y/n)")
            if ok in ("y", "Y") or ok in ("n", "N") : 
                done= True # probably better
                raise GetOutOfLoop
        # other stuff
except GetOutOfLoop:
    pass

Pour cet exemple précis, une exception n'est peut-être pas nécessaire.

D'autre part, nous avons souvent des options "Y", "N" et "Q" dans les applications en mode caractère. Pour l'option "Q", nous voulons une sortie immédiate. C'est plus exceptionnel.

9 votes

Sérieusement, les exceptions sont extrêmement Python, bon marché et idiomatique, en utilise beaucoup, beaucoup. Il est également très facile d'en définir et d'en lancer des personnalisés.

18 votes

Une idée intéressante. Je ne sais pas si je vais l'aimer ou la détester.

9 votes

Cette solution serait plus utile si elle présentait les deux variantes séparément. (1) en utilisant un drapeau ( done ). (2) la levée d'une exception. Les fusionner en une seule solution ne fait que compliquer les choses. Pour les futurs lecteurs : SOIT utiliser toutes les lignes impliquant done OU définir GetOutOfLoop(Exception) et augmenter/excepter cela.

60voto

Mark Dickinson Points 6780

Je suis plutôt d'accord pour dire que le refactoring dans une fonction est généralement la meilleure approche pour ce genre de situation, mais pour ce qui est des cas où vous vraiment ont besoin de sortir des boucles imbriquées, voici une variante intéressante de l'approche de levée d'exception décrite par @S.Lott. Elle utilise la fonction with pour que la levée des exceptions soit un peu plus agréable. Définissez un nouveau gestionnaire de contexte (vous ne devez le faire qu'une seule fois) avec :

from contextlib import contextmanager
@contextmanager
def nested_break():
    class NestedBreakException(Exception):
        pass
    try:
        yield NestedBreakException
    except NestedBreakException:
        pass

Vous pouvez maintenant utiliser ce gestionnaire de contexte comme suit :

with nested_break() as mylabel:
    while True:
        print "current state"
        while True:
            ok = raw_input("Is this ok? (y/n)")
            if ok == "y" or ok == "Y": raise mylabel
            if ok == "n" or ok == "N": break
        print "more processing"

Avantages : (1) c'est un peu plus propre (pas de bloc try-except explicite), et (2) vous obtenez un bloc Exception sous-classe pour chaque utilisation de nested_break ; il n'est pas nécessaire de déclarer ses propres Exception à chaque fois.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X