Sortir de plusieurs boucles for imbriquées en Python

Le mot clé break permet de sortir d’une boucle for abruptement. Mais une seule. Parfois on a 3, 4 boucles imbriquées, et on aimerait tellement sortir de toutes d’un coup.

Ce que je vais vous montrer est mal. Mais c’est tellement bon.

# on fait une exception qui hérite de StopIteration car c'est ce qui est utilisé
# de toute façon pour arrêter une boucle
class MultiStopIteration(StopIteration):
    # la classe est capable de se lever elle même comme exception
    def throw(self):
        raise self
 
 
@contextmanager
def multibreak():
    # le seul boulot de notre context manager c'est de donne le moyen de lever
    # l'exception tout en l'attrapant
    try:
        yield MultiStopIteration().throw
    except MultiStopIteration:
        pass

En gros on se créé un petit context manager, dont le seul but est de créer une exception qui va remonter en pêtant toutes les boucles. Je vous avais dit que c’était mal

Ca s’utilise comme ça:

>>> with multibreak() as stop:
...     for x in range(1, 4):
...         for z in range(1, 4):
...             for w in range(1, 4):
...                 print w
...                 if x * z * w == 2 * 2 * 2:
...                     print 'stop'
...                     stop() # appel MultiStopIteration().throw()
...
1
2
3
1
2
3
1
2
3
1
2
3
1
2
stop

Je vous avais dit que ça serait bon.

A part le fait que ce n’est pas très rapide au moment du bubbling de l’exception sur 3 blocks, il n’y a aucun danger ou side-effect. On triche en fait à peine, car le mécanisme interne des boucles en Python utilise de toute façon déjà une exception (StopIteration) pour dire à une boucle quand s’arrêter.

Bref, encore une victoire de connard.

No related posts.

flattr this!

9 comments

  1. Trés bon ça. Je le range avec le pied de biche.
    Si t’en a d’autres, hésite pas.

  2. Je vous AVAIT DIT que ça serait bon. :)

  3. Christophe Simonis

    Sérieusement?

    La solution clean est de sortir toutes ces boucles dans une fonction et d’utiliser `return`.

  4. Ou encore mieux d’utiliser un générateur et itertools.takewhile. Mais la sodomie n’est pas propre non plus, c’est pas pour ça que de temps en temps…

  5. @c0da. J’étais bourré quand j’ai écris l’article. Et puis c’est “avaiS dit” d’abord ! :-p

  6. Sebastien

    Gros sale ;-). Bon, cela a été officiellement rejeté par le dictateur bénébole à vie :
    http://www.python.org/dev/peps/pep-3136/

    Sinon, pourquoi ne pas lever simplement une exception ? C’est moins compliqué…

  7. Mais c’est ce qu’on fait, lever une exception.

  8. Kontre

    Je pense qu’il parle d’un des exemples donnés dans le pep 3136, où on ne lève qu’une seule exception:

    class BreakOutOfALoop(Exception): pass
     
    try:
        for a in a_list:
            for b in b_list:
                if condition_one(a, b):
                    raise BreakOutOfALoop
    except BreakOutOfALoop:
        break

    (Meh, ils sont où les espaces, et la coloration ?)

  9. Kontre

    (Ah, les espaces et la coloration syntaxique ne se voient pas dans la prévisualisation)

Flux RSS des commentaires

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Jouer à mario en attendant que les autres répondent