web-dev-qa-db-fra.com

Comment sortir de plusieurs boucles en Python?

Étant donné le code suivant (cela ne fonctionne pas):

while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": break 2 #this doesn't work :(
        if ok == "n" or ok == "N": break
    #do more processing with menus and stuff

Y at-il un moyen de faire ce travail? Ou dois-je faire un contrôle pour sortir de la boucle d'entrée, puis un autre, plus limité, vérifier dans la boucle extérieure pour sortir tous ensemble si l'utilisateur est satisfait?

Edit-FYI: get_input est une fonction courte que j'ai écrite et qui permet d'afficher une invite et des valeurs par défaut ainsi que tout ce qui est fantaisiste et qui renvoie stdin.readline().strip()

360
Matthew Scharley

Mon premier instinct serait de refactoriser la boucle imbriquée dans une fonction et d’utiliser return pour sortir. 

402
Robert Rossney

Voici une autre approche qui est courte. L'inconvénient est que vous ne pouvez que casser la boucle externe, 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 à: Pourquoi python utilise-t-il 'else' après les boucles for et while?

Information clé: Il ne semble que comme si la boucle externe se cassait toujours. Mais si la boucle interne ne se rompt pas, la boucle externe ne le sera pas non plus. 

La déclaration continue est la magie ici. C'est dans la clause for-else. Par définition cela se produit s'il n'y a pas de rupture intérieure. Dans cette situation, continue contourne nettement la rupture externe.

145
yak

PEP 3136 propose la mention "pause/continue". Guido l'a rejetée car "le code si compliqué à exiger cette fonctionnalité est très rare". Le PEP mentionne cependant certaines solutions de contournement (telles que la technique des exceptions), tandis que Guido estime que le refactoring pour utiliser le retour sera plus simple dans la plupart des cas.

136
John Fouhy

Premièrement, la logique ordinaire est utile. 

Si, pour une raison quelconque, les conditions de terminaison ne peuvent pas être définies, les exceptions constituent un plan de secours. 

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 spécifique, une exception peut ne pas être nécessaire. 

D'autre part, nous avons souvent les 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.

99
S.Lott

J'ai tendance à être d'accord sur le fait que refactoriser une fonction est généralement la meilleure approche pour ce genre de situation, mais lorsque vous avez réellement besoin de sortir de boucles imbriquées , voici une variante intéressante de l'approche d'exception décrite par @ S.Lott. Il utilise l'instruction with de Python pour rendre l'apparence de l'exception 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) il est légèrement plus propre (pas de bloc try-except explicite), et (2) vous obtenez une sous-classe Exception construite sur mesure pour chaque utilisation de nested_break; pas besoin de déclarer votre propre sous-classe Exception à chaque fois.

49
Mark Dickinson

Tout d'abord, vous pouvez également envisager de transformer le processus d'obtention et de validation de l'entrée en une fonction; dans cette fonction, vous pouvez simplement renvoyer la valeur si elle est correcte et continuer à tourner dans la boucle while sinon. Cela évite essentiellement le problème que vous avez résolu, et peut généralement être appliqué dans le cas plus général (rupture de plusieurs boucles). Si vous devez absolument conserver cette structure dans votre code et que vous ne voulez vraiment pas vous occuper des booléens comptables ...

Vous pouvez aussi utiliser goto de la manière suivante (en utilisant un module April Fools de ici ):

#import the stuff
from goto import goto, label

while True:
    #snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": goto .breakall
        if ok == "n" or ok == "N": break
    #do more processing with menus and stuff
label .breakall

Je sais, je sais, "tu n'utiliseras pas de goto" et tout ça, mais ça marche bien dans des cas aussi étranges que celui-ci.

36
Matt J

Introduisez une nouvelle variable que vous utiliserez comme "interrupteur de boucle". Attribuez-lui d’abord quelque chose (False, 0, etc.), puis, dans la boucle externe, avant de vous en séparer, modifiez la valeur en autre chose (True, 1, ...). Une fois la boucle terminée, la boucle «parent» vérifie cette valeur. Permettez-moi de démontrer:

breaker = False #our mighty loop exiter!
while True:
    while True:
        if conditionMet:
            #insert code here...
            breaker = True 
            break
    if breaker: # the interesting part!
        break   # <--- !

Si vous avez une boucle infinie, c'est le seul moyen de sortir; pour d'autres boucles, l'exécution est vraiment beaucoup plus rapide. Cela fonctionne également si vous avez plusieurs boucles imbriquées. Vous pouvez sortir tous ou juste quelques uns. Des possibilités infinies! J'espère que cela a aidé!

25
krvolok

keeplooping=True
while keeplooping:
    #Do Stuff
    while keeplooping:
          #do some other stuff
          if finisheddoingstuff(): keeplooping=False

ou quelque chose comme ça. Vous pouvez définir une variable dans la boucle interne et la vérifier dans la boucle externe immédiatement après la sortie de la boucle interne, le cas échéant éventuellement en rupture. J'aime un peu la méthode GOTO, à condition que l'utilisation du module de plaisanterie d'un poisson d'avril ne vous dérange pas - ce n'est pas Pythonic, mais c'est logique.

14
quick_dry

Ce n'est pas la meilleure façon de le faire, mais à mon avis, c'est la meilleure. 

def loop():
    while True:
    #snip: print out current state
        while True:
            ok = get_input("Is this ok? (y/n)")
            if ok == "y" or ok == "Y": return
            if ok == "n" or ok == "N": break
        #do more processing with menus and stuff

Je suis sûr que vous pouvez également trouver une solution en utilisant la récursivité, mais je ne sais pas si c'est une bonne option pour vous.

11
Jason Baker

Pour sortir de plusieurs boucles imbriquées, sans refactoring dans une fonction, utilisez une "instruction goto simulée" avec l’exception StopIteration intégrée :

try:
    for outer in range(100):
        for inner in range(100):
            if break_early():
                raise StopIteration

except StopIteration: pass

Voir cette discussion sur l’utilisation des instructions goto pour sortir des boucles imbriquées.

10
Justas

Intégrez votre logique de boucle dans un itérateur qui renvoie les variables de boucle et les renvoie une fois l'opération terminée. En voici un simple: il présente les images en rangées/colonnes jusqu'à ce que nous ayons épuisé les images ou les placent:

def it(rows, cols, images):
    i = 0
    for r in xrange(rows):
        for c in xrange(cols):
            if i >= len(images):
                return
            yield r, c, images[i]
            i += 1 

for r, c, image in it(rows=4, cols=4, images=['a.jpg', 'b.jpg', 'c.jpg']):
    ... do something with r, c, image ...

Cela a l’avantage de diviser la logique de boucle complexe et le traitement des données ...

8
Matt Billenstein

Et pourquoi ne pas continuer à boucler si deux conditions sont vraies? Je pense que cela est plus pythonique:

dejaVu = True

while dejaVu:
    while True:
        ok = raw_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y" or ok == "n" or ok == "N":
            dejaVu = False
            break

N'est-ce pas?

Bonne chance.

7
Mauro Aspé

Dans ce cas, comme l'ont également souligné d'autres personnes, la décomposition fonctionnelle est la solution. Code en Python 3:

def user_confirms():
    while True:
        answer = input("Is this OK? (y/n) ").strip().lower()
        if answer in "yn":
            return answer == "y"

def main():
    while True:
        # do stuff
        if user_confirms():
            break
3
Loax

Il y a une astuce cachée dans la structure Python while ... else qui peut être utilisée pour simuler la double coupure sans beaucoup de changements/ajouts de code. En substance, si la condition while est false, le bloc else est déclenché. Aucune exception, continue ou break ne déclenche le bloc else. Pour plus d'informations, voir les réponses à la clause " Else sur Python tant que l'instruction ", ou la documentation Python sur while (v2.7) .

while True:
    #snip: print out current state
    ok = ""
    while ok != "y" and ok != "n":
        ok = get_input("Is this ok? (y/n)")
        if ok == "n" or ok == "N":
            break    # Breaks out of inner loop, skipping else

    else:
        break        # Breaks out of outer loop

    #do more processing with menus and stuff

Le seul inconvénient est que vous devez déplacer la condition de rupture double dans la condition while (ou ajouter une variable de drapeau). Il en existe également des variantes pour la boucle for, où le bloc else est déclenché après la fin de la boucle.

3
holroy

Une autre façon de réduire votre itération à une boucle à un seul niveau serait d'utiliser des générateurs, comme spécifié également dans le paramètre python reference

for i, j in ((i, j) for i in A for j in B):
    print(i , j)
    if (some_condition):
        break

Vous pouvez l'adapter à un nombre quelconque de niveaux pour la boucle

L'inconvénient est que vous ne pouvez plus briser un seul niveau. C'est tout ou rien.

Un autre inconvénient est que cela ne fonctionne pas avec une boucle while. À l’origine, je voulais publier cette réponse sur Python - «rompre» toutes les boucles mais malheureusement, cette copie est fermée. 

2
Peeyush Kushwaha

Ma raison de venir ici est que j'avais une boucle externe et une boucle interne comme celle-ci:

for x in array:
  for y in dont_use_these_values:
    if x.value==y:
      array.remove(x)  # fixed, was array.pop(x) in my original answer
      continue

  do some other stuff with x

Comme vous pouvez le constater, cela n'ira pas réellement au prochain x, mais ira au prochain y à la place.

ce que j'ai trouvé pour résoudre ce problème était simplement de parcourir le tableau deux fois à la place:

for x in array:
  for y in dont_use_these_values:
    if x.value==y:
      array.remove(x)  # fixed, was array.pop(x) in my original answer
      continue

for x in array:
  do some other stuff with x

Je sais que c’était un cas particulier de la question d’OP, mais je l’affiche dans l’espoir que cela aidera une personne à penser son problème différemment tout en gardant les choses simples.

2
Nathan Garabedian

En utilisant une fonction:

def myloop():
    for i in range(1,6,1):  # 1st loop
        print('i:',i)
        for j in range(1,11,2):  # 2nd loop
            print('   i, j:' ,i, j)
            for k in range(1,21,4):  # 3rd loop
                print('      i,j,k:', i,j,k)
                if i%3==0 and j%3==0 and k%3==0:
                    return  # getting out of all loops

myloop()

Essayez d’exécuter les codes ci-dessus en commentant la return.

Sans utiliser aucune fonction:

done = False
for i in range(1,6,1):  # 1st loop
    print('i:', i)
    for j in range(1,11,2):  # 2nd loop
        print('   i, j:' ,i, j)
        for k in range(1,21,4):  # 3rd loop
            print('      i,j,k:', i,j,k)
            if i%3==0 and j%3==0 and k%3==0:
                done = True
                break  # breaking from 3rd loop
        if done: break # breaking from 2nd loop
    if done: break     # breaking from 1st loop

Maintenant, lancez les codes ci-dessus tels quels, puis essayez-les en commentant chaque ligne contenant break une par une à partir du bas.

2
Rafiq
break_label = None
while True:
    # snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y":
            break_label = "outer"   # specify label to break to
            break
        if ok == "n" or ok == "N":
            break
    if break_label:
        if break_label != "inner":
            break                   # propagate up
        break_label = None          # we have arrived!
if break_label:
    if break_label != "outer":
        break                       # propagate up
    break_label = None              # we have arrived!

#do more processing with menus and stuff
1
RufusVS

probablement petite astuce comme ci-dessous fera sinon préférer refactorial dans la fonction

ajout d'une variable break_level pour contrôler la condition de la boucle while

break_level = 0
# while break_level < 3: # if we have another level of nested loop here
while break_level < 2:
    #snip: print out current state
    while break_level < 1:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y": break_level = 2 # break 2 level
        if ok == "n" or ok == "N": break_level = 1 # break 1 level
1
Skycc
break_levels = 0
while True:
    # snip: print out current state
    while True:
        ok = get_input("Is this ok? (y/n)")
        if ok == "y" or ok == "Y":
            break_levels = 1        # how far nested, excluding this break
            break
        if ok == "n" or ok == "N":
            break                   # normal break
    if break_levels:
        break_levels -= 1
        break                       # pop another level
if break_levels:
    break_levels -= 1
    break

# ...and so on
1
RufusVS

Essayez d'utiliser un générateur infini.

from itertools import repeat
inputs = (get_input("Is this ok? (y/n)") for _ in repeat(None))
response = (i.lower()=="y" for i in inputs if i.lower() in ("y", "n"))

while True:
    #snip: print out current state
    if next(response):
        break
    #do more processing with menus and stuff
1
robert king

Vous pouvez définir une variable (par exemple break_statement ), puis la remplacer par une valeur différente lorsqu’une condition de rupture est définie et l’utiliser dans l’instruction if pour interrompre également la deuxième boucle. 

while True:
    break_statement=0
    while True:
        ok = raw_input("Is this ok? (y/n)")
        if ok == "n" or ok == "N": 
            break
        if ok == "y" or ok == "Y": 
            break_statement=1
            break
    if break_statement==1:
        break
1
helmsdeep

Un moyen simple de transformer plusieurs boucles en une seule boucle sécable consiste à utiliser numpy.ndindex

for i in range(n):
  for j in range(n):
    val = x[i, j]
    break # still inside the outer loop!

for i, j in np.ndindex(n, n):
  val = x[i, j]
  break # you left the only loop there was!

Vous devez indexer dans vos objets, par opposition à pouvoir parcourir les valeurs de manière explicite, mais au moins dans les cas simples, il semble être environ 2 à 20 fois plus simple que la plupart des réponses suggérées.

0
one_observation

Je voudrais vous rappeler que des fonctions en Python peuvent être créées au milieu du code et peuvent accéder aux variables environnantes de manière transparente pour la lecture et avec la déclaration nonlocal ou global pour l'écriture.

Vous pouvez donc utiliser une fonction en tant que "structure de contrôle sécable", définissant un lieu vers lequel vous souhaitez revenir:

def is_prime(number):

    foo = bar = number

    def return_here():
        nonlocal foo, bar
        init_bar = bar
        while foo > 0:
            bar = init_bar
            while bar >= foo:
                if foo*bar == number:
                    return
                bar -= 1
            foo -= 1

    return_here()

    if foo == 1:
        print(number, 'is prime')
    else:
        print(number, '=', bar, '*', foo)

>>> is_prime(67)
67 is prime
>>> is_prime(117)
117 = 13 * 9
>>> is_prime(16)
16 = 4 * 4
0
user

Des solutions de 2 manières

Avec un exemple: Ces deux matrices sont-elles égales/identiques?
matrix1 et matrix2 ont la même taille, n, 2 matrices dimensionnelles.

Première solution , sans fonction

same_matrices = True
inner_loop_broken_once = False
n = len(matrix1)

for i in range(n):
    for j in range(n):

        if matrix1[i][j] != matrix2[i][j]:
            same_matrices = False
            inner_loop_broken_once = True
            break

    if inner_loop_broken_once:
        break

Deuxième solution , avec une fonction
Ceci est la solution finale pour mon cas

def are_two_matrices_the_same (matrix1, matrix2):
    n = len(matrix1)
    for i in range(n):
        for j in range(n):
            if matrix1[i][j] != matrix2[i][j]:
                return False
    return True

Bonne journée!

0
Harun Altay

Espérons que cela aide:

x = True
y = True
while x == True:
    while y == True:
         ok = get_input("Is this ok? (y/n)") 
         if ok == "y" or ok == "Y":
             x,y = False,False #breaks from both loops
         if ok == "n" or ok == "N": 
             break #breaks from just one
0
Daniel L.