Python: Continuer à l'itération suivante dans la boucle externe

135

Je voulais savoir s'il existe des moyens intégrés pour continuer à l'itération suivante dans la boucle externe en python. Par exemple, considérez le code:

for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            continue
    ...block1...

Je veux que cette instruction continue quitte la boucle jj et passe à l'élément suivant de la boucle ii. Je peux implémenter cette logique d'une autre manière (en définissant une variable d'indicateur), mais y a-t-il un moyen facile de le faire, ou est-ce comme demander trop?

Sahas
la source
11
Il existe en fait une instruction goto fonctionnelle pour Python: entrian.com/goto . Il a été publié comme une blague de poisson d'avril :-), mais est censé fonctionner.
codeape
3
Oh, s'il vous plaît, n'utilisez pas cette blague! C'est remarquablement intelligent, mais vous serez triste plus tard si vous le mettez dans votre code.
Ned Batchelder

Réponses:

71
for i in ...:
    for j in ...:
        for k in ...:
            if something:
                # continue loop i

Dans un cas général, lorsque vous avez plusieurs niveaux de bouclage et break cela ne fonctionne pas pour vous (parce que vous voulez continuer une des boucles supérieures, pas celle juste au-dessus de la boucle actuelle), vous pouvez effectuer l'une des opérations suivantes

Refactoriser les boucles dont vous souhaitez vous échapper dans une fonction

def inner():
    for j in ...:
        for k in ...:
            if something:
                return


for i in ...:
    inner()

L'inconvénient est que vous devrez peut-être passer à cette nouvelle fonction certaines variables, qui étaient auparavant dans la portée. Vous pouvez soit simplement les passer en tant que paramètres, en faire des variables d'instance sur un objet (créer un nouvel objet juste pour cette fonction, si cela a du sens), ou des variables globales, des singletons, peu importe (ehm, ehm).

Ou vous pouvez définir innercomme fonction imbriquée et la laisser capturer simplement ce dont elle a besoin (peut-être plus lente?)

for i in ...:
    def inner():
        for j in ...:
            for k in ...:
                if something:
                    return
    inner()

Utiliser des exceptions

Philosophiquement, c'est à cela que servent les exceptions, interrompant le flux du programme à travers les blocs de construction de programmation structurée (si, pendant, pendant) lorsque cela est nécessaire.

L'avantage est que vous n'avez pas à diviser le morceau de code en plusieurs parties. C'est bien s'il s'agit d'une sorte de calcul que vous concevez en l'écrivant en Python. Introduire des abstractions à ce stade précoce peut vous ralentir.

La mauvaise chose avec cette approche est que les auteurs d'interprètes / compilateurs supposent généralement que les exceptions sont exceptionnelles et optimisées pour elles en conséquence.

class ContinueI(Exception):
    pass


continue_i = ContinueI()

for i in ...:
    try:
        for j in ...:
            for k in ...:
                if something:
                    raise continue_i
    except ContinueI:
        continue

Créez une classe d'exception spéciale pour cela, afin de ne pas risquer de faire taire accidentellement une autre exception.

Quelque chose d'autre entièrement

Je suis sûr qu'il existe encore d'autres solutions.

user7610
la source
Je n'arrive pas à croire que je n'ai pas pensé à déplacer ma deuxième boucle vers une autre méthode. Je deviens lent
pmccallum
1
Pour moi, utiliser les exceptions est un bon moyen d'y parvenir. Je suis d'accord avec @ user7610 - "philosophiquement, c'est à cela que servent les exceptions".
Renato Byrro
Bonne vieille variable booléenne et instructions If?
MrR le
L'OP recherche une solution alternative à cela, "Je peux implémenter cette logique d'une autre manière (en définissant une variable d'indicateur) [...]"
user7610
149
for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            break
    else:
        ...block1...

Break cassera la boucle interne, et block1 ne sera pas exécuté (il ne fonctionnera que si la boucle interne est quittée normalement).

culebrón
la source
1
Salut, y a-t-il d'autres options comme celle-ci? Parce que je veux faire une autre boucle for dans block1, et comme ça mon code irait à 3 niveaux de profondeur. Situation étrange.
Sahas
3
Pour moi, cela semble que vous essayez de faire quelque chose avec des boucles for qui seraient mieux abordées d'une manière différente ...
Kimvais
Oui. C'est pourquoi je n'ai pas utilisé la structure for..else. Maintenant, j'aurais encore besoin de boucles, mais j'utiliserai des variables d'indicateur pour détourner le contrôle.
Sahas
3
for...elseest souvent une construction utile, même si elle peut prêter à confusion. N'oubliez pas que cela elsesignifie "pas de pause" dans ce contexte.
asmeurer
Cela semble être limité à seulement deux couches de boucles. J'ai besoin de mettre à jour du code qui a trois boucles imbriquées, et une nouvelle exigence du client signifie que dans certaines circonstances, la boucle la plus interne doit continuer la prochaine itération de la boucle la plus externe. Je suppose que votre suggestion ne s'appliquerait pas à ce scénario.
kasperd
42

Dans d'autres langues, vous pouvez étiqueter la boucle et sortir de la boucle étiquetée. La proposition d'amélioration de Python (PEP) 3136 a suggéré de les ajouter à Python mais Guido l'a rejetée :

Cependant, je le rejette au motif que le code si compliqué d'exiger cette fonctionnalité est très rare. Dans la plupart des cas, il existe des solutions de contournement qui produisent du code propre, par exemple en utilisant «return». Bien que je sois sûr qu'il existe de (rares) cas réels où la clarté du code souffrirait d'un refactoring permettant d'utiliser le retour, cela est compensé par deux problèmes:

  1. La complexité ajoutée à la langue, en permanence. Cela affecte non seulement toutes les implémentations Python, mais également tous les outils d'analyse de source, et bien sûr toute la documentation du langage.

  2. Je m'attends à ce que la fonctionnalité soit plus abusée qu'elle ne sera utilisée correctement, ce qui entraînera une nette diminution de la clarté du code (mesurée sur tout le code Python écrit désormais). Les programmeurs paresseux sont partout, et avant que vous ne le sachiez, vous avez un désordre incroyable entre les mains de code inintelligible.

Donc, si c'est ce que vous espériez, vous n'avez pas de chance, mais regardez l'une des autres réponses car il y a de bonnes options.

Dave Webb
la source
4
Intéressant. Je suis d'accord avec Guido ici. Même si ce serait bien dans certains cas, il y aurait des abus. Une autre raison pour ne pas l'implémenter est que pour le moment, il est assez simple de transférer du code entre C et Python. Une fois que Python commence à récupérer des fonctionnalités qui manquent à d'autres langages, cela devient plus difficile. Prenons par exemple le fait que vous pouvez avoir une instruction else sur une boucle for en Python ... cela rend le code moins portable vers d'autres langages.
eric.frederich
2
Tous saluent Guido notre BDFL
JnBrymn
4
C'est plus un héritier rouge qu'un bon contre-argument, mais il me semble que le comportement de for-elseest plus compliqué, plus difficile à lire et probablement plus abusé (sinon une erreur pure et simple) que ne le seraient les boucles nommées. Je pense que j'aurais utilisé un mot-clé différent de else- peut-être que quelque chose comme ça resumeaurait été bien? Vous êtes breakdans la boucle et resumec'est juste après?
ArtOfWarfare
5
Cela me rend triste. Je ne peux pas croire à quel point j'aime et déteste Python en même temps. Si beau, mais si wtf.
jlh
5
@jlh Surtout wtf pour moi. Parfois, je pense qu'il veut être différent non pas dans un but légitime, mais simplement pour être différent. C'est un bon exemple de cela. Je rencontre assez souvent le besoin de casser les boucles extérieures.
Rikaelus
14

Je pense que vous pourriez faire quelque chose comme ça:

for ii in range(200):
    restart = False
    for jj in range(200, 400):
        ...block0...
        if something:
            restart = True
            break
    if restart:
        continue
    ...block1...
asmeureur
la source
4
-1: Le PO a clairement déclaré qu'il savait qu'il pouvait faire quelque chose comme ça, et cela ressemble juste à une version plus désordonnée de la réponse acceptée (qui est antérieure à la vôtre de 8 mois, donc il ne pouvait pas être que vous venez de manquer le répondre).
ArtOfWarfare
10
La réponse acceptée n'est pas plus claire si vous ne l' avez jamais vu for, elseavant (et je pense que même la plupart des gens qui ont ne me souviens pas du haut de leur tête la façon dont cela fonctionne).
asmeurer le
3

Je pense que l’un des moyens les plus simples d’y parvenir est de remplacer l’instruction «continuer» par «casser», c’est-à-dire

for ii in range(200):
 for jj in range(200, 400):
    ...block0...
    if something:
        break
 ...block1...       

Par exemple, voici le code simple pour voir comment cela se passe exactement:

for i in range(10):
    print("doing outer loop")
    print("i=",i)
    for p in range(10):
        print("doing inner loop")
        print("p=",p)
        if p==3:
            print("breaking from inner loop")
            break
    print("doing some code in outer loop")
Khelina Fedorchuk
la source
2

Une autre façon de traiter ce genre de problème est d'utiliser Exception ().

for ii in range(200):
    try:
        for jj in range(200, 400):
            ...block0...
            if something:
                raise Exception()
    except Exception:
        continue
    ...block1...

Par exemple:

for n in range(1,4):
    for m in range(1,4):
        print n,'-',m

résultat:

    1-1
    1-2
    1-3
    2-1
    2-2
    2-3
    3-1
    3-2
    3-3

En supposant que nous voulons sauter à la boucle n externe à partir de m boucle si m = 3:

for n in range(1,4):
    try:
        for m in range(1,4):
            if m == 3:
                raise Exception()            
            print n,'-',m
    except Exception:
        continue

résultat:

    1-1
    1-2
    2-1
    2-2
    3-1
    3-2

Lien de référence: http://www.programming-idioms.org/idiom/42/continue-outer-loop/1264/python

Patrick
la source
1

Nous voulons trouver quelque chose puis arrêter l'itération interne. J'utilise un système de drapeau.

for l in f:
    flag = True
    for e in r:
        if flag==False:continue
        if somecondition:
            do_something()
            flag=False
Esther
la source
Je ne sais pas pourquoi votre solution a été rejetée; quelqu'un a publié essentiellement exactement la même chose et a été voté 10 fois
Locane
Je n'ai pas beaucoup de chance avec stackoverflow.
Esther
1
Peut-être parce qu'il y a déjà exactement la même chose publiée ici, je suppose ... Et le False:continueproblème est ... un formatage inhabituel. Comme c'est souvent le cas dans les systèmes «naturels» où l'exponentielle est la norme, il suffit d'avoir de la chance quelques fois sur SO pour accumuler une quantité significative de points de réputation. Quoi qu'il en soit, mes «meilleures» réponses sont généralement les moins populaires.
user7610
0

J'ai juste fait quelque chose comme ça. Ma solution pour cela était de remplacer la boucle for intérieure par une compréhension de liste.

for ii in range(200):
    done = any([op(ii, jj) for jj in range(200, 400)])
    ...block0...
    if done:
        continue
    ...block1...

où op est un opérateur booléen agissant sur une combinaison de ii et jj. Dans mon cas, si l'une des opérations retournait vraie, c'était terminé.

Ce n'est vraiment pas si différent de décomposer le code en une fonction, mais j'ai pensé qu'utiliser l'opérateur "any" pour faire un OU logique sur une liste de booléens et faire la logique en une seule ligne était intéressant. Cela évite également l'appel de fonction.

cagem12
la source