Émuler une boucle do-while en Python?

799

J'ai besoin d'émuler une boucle do-while dans un programme Python. Malheureusement, le code simple suivant ne fonctionne pas:

list_of_ints = [ 1, 2, 3 ]
iterator = list_of_ints.__iter__()
element = None

while True:
  if element:
    print element

  try:
    element = iterator.next()
  except StopIteration:
    break

print "done"

Au lieu de "1,2,3, terminé", il imprime la sortie suivante:

[stdout:]1
[stdout:]2
[stdout:]3
None['Traceback (most recent call last):
', '  File "test_python.py", line 8, in <module>
    s = i.next()
', 'StopIteration
']

Que puis-je faire pour intercepter l'exception «stop itération» et rompre correctement une boucle while?

Un exemple de la raison pour laquelle une telle chose peut être nécessaire est illustré ci-dessous sous le nom de pseudocode.

État machine:

s = ""
while True :
  if state is STATE_CODE :
    if "//" in s :
      tokens.add( TOKEN_COMMENT, s.split( "//" )[1] )
      state = STATE_COMMENT
    else :
      tokens.add( TOKEN_CODE, s )
  if state is STATE_COMMENT :
    if "//" in s :
      tokens.append( TOKEN_COMMENT, s.split( "//" )[1] )
    else
      state = STATE_CODE
      # Re-evaluate same line
      continue
  try :
    s = i.next()
  except StopIteration :
    break
grigoryvp
la source
4
Hum ... Ce n'est pas un "do-while" approprié; c'est simplement un "do-forever". Qu'est-ce qui ne va pas avec "tandis que True" et "pause"?
S.Lott
70
S. Lott: Je suis presque sûr que sa question était de savoir comment implémenter do en python. Donc, je ne m'attendrais pas à ce que son code soit complètement correct. De plus, il est très proche d'un do tandis que ... il vérifie une condition à la fin de la boucle "pour toujours" pour voir s'il doit éclater. Ce n'est pas "do-forever".
Tom
4
donc ... votre exemple de code initial fonctionne réellement pour moi sans problème et je ne reçois pas ce traceback. c'est un idiome approprié pour une boucle do while où la condition de rupture est l'épuisement de l'itérateur. En règle générale, vous devez définir s=i.next()plutôt que Aucun et éventuellement effectuer un travail initial plutôt que de simplement rendre votre premier passage dans la boucle inutile.
underrun
3
@underrun Malheureusement, la publication n'est pas étiquetée avec la version de Python utilisée - l'extrait d'origine fonctionne aussi pour moi en utilisant 2.7, probablement en raison de mises à jour du langage Python lui-même.
Hannele

Réponses:

985

Je ne sais pas trop ce que vous essayez de faire. Vous pouvez implémenter une boucle do-while comme ceci:

while True:
  stuff()
  if fail_condition:
    break

Ou:

stuff()
while not fail_condition:
  stuff()

Que faites-vous en essayant d'utiliser une boucle do while pour imprimer le contenu de la liste? Pourquoi ne pas simplement utiliser:

for i in l:
  print i
print "done"

Mise à jour:

Avez-vous une liste de lignes? Et vous voulez continuer à le parcourir? Que diriez-vous:

for s in l: 
  while True: 
    stuff() 
    # use a "break" instead of s = i.next()

Cela semble-t-il quelque chose de proche de ce que vous voudriez? Avec votre exemple de code, ce serait:

for s in some_list:
  while True:
    if state is STATE_CODE:
      if "//" in s:
        tokens.add( TOKEN_COMMENT, s.split( "//" )[1] )
        state = STATE_COMMENT
      else :
        tokens.add( TOKEN_CODE, s )
    if state is STATE_COMMENT:
      if "//" in s:
        tokens.append( TOKEN_COMMENT, s.split( "//" )[1] )
        break # get next s
      else:
        state = STATE_CODE
        # re-evaluate same line
        # continues automatically
À M
la source
1
j'ai besoin de créer une machine d'état. Dans la machine d'état, il est normal de réévaluer l'instruction CURRENT, j'ai donc besoin de «continuer» sans itérer l'élément suivant. Je ne sais pas comment faire une telle chose dans 'for s in l:' itération :(. Dans la boucle do-while, 'continue' réévaluera l'élément actuel, itération à la fin
grigoryvp
Voulez-vous dire que vous devez garder une trace de votre place dans la liste? De cette façon, lorsque vous revenez au même état, vous pouvez reprendre là où vous vous étiez arrêté? Donnez un peu plus de contexte. Il semble que vous feriez mieux d'utiliser un index dans la liste.
Tom
Merci, j'ai commenté votre pseudocode ... votre exemple semble plutôt mauvais puisque vous semblez gérer "//" de la même manière quel que soit l'état dans lequel vous vous trouvez. De plus, est-ce ce vrai code où vous traitez les commentaires? Et si vous avez des chaînes avec des barres obliques? ie: print "blah // <- est-ce que ça vous gâche?"
Tom
4
C'est dommage que python n'ait pas de boucle do-while. Python est SEC, hein?
Kr0e
43
Voir également PEP 315 pour la position / justification officielle: "Les utilisateurs de la langue sont invités à utiliser la forme while-True avec un if-break interne lorsqu'une boucle do-while aurait été appropriée."
dtk
311

Voici un moyen très simple d'émuler une boucle do-while:

condition = True
while condition:
    # loop body here
    condition = test_loop_condition()
# end of loop

Les principales caractéristiques d'une boucle do-while sont que le corps de la boucle s'exécute toujours au moins une fois et que la condition est évaluée au bas du corps de la boucle. La structure de contrôle montrée ici accomplit ces deux opérations sans avoir besoin d'exceptions ou d'instructions break. Il introduit une variable booléenne supplémentaire.

flacon de poudre
la source
11
Il n'ajoute pas toujours une variable booléenne supplémentaire. Souvent, il existe déjà quelque chose (s) dont l'état peut être testé.
martineau
14
La raison pour laquelle j'aime le plus cette solution est qu'elle n'ajoute aucune autre condition, il s'agit toujours d'un seul cycle, et si vous choisissez un bon nom pour la variable d'assistance, la structure entière est assez claire.
Roberto
4
REMARQUE: bien que cela réponde à la question d'origine, cette approche est moins flexible que l'utilisation break. Plus précisément, s'il y a une logique nécessaire APRÈS test_loop_condition(), qui ne doit pas être exécutée une fois que nous avons terminé, elle doit être enveloppée if condition:. BTW, conditionest vague. Plus descriptif: moreou notDone.
ToolmakerSteve
7
@ToolmakerSteve Je ne suis pas d'accord. J'utilise rarement breakdans les boucles et quand je le rencontre dans du code que je maintiens, je trouve que la boucle, le plus souvent, aurait pu être écrite sans elle. La solution présentée est, IMO, la manière la plus claire de représenter une construction do while en python.
nonsensickle
1
Idéalement, la condition sera nommée quelque chose de descriptif, comme has_no_errorsou end_reached(auquel cas la boucle commenceraitwhile not end_reached
Josiah Yoder
75

Mon code ci-dessous pourrait être une implémentation utile, soulignant la principale différence entre contre si je comprends bien.

Donc, dans ce cas, vous parcourez toujours la boucle au moins une fois.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()
evan54
la source
2
Bonne réponse, je dirais. De plus, il évite les cassures , pour une utilisation sûre dans les blocs try / except.
Zv_oDD
le jit / optimizer évite-t-il de re-tester first_pass après la première passe? sinon, ce serait un problème de performance ennuyeux, bien que peut-être mineur
markhahn
2
@markhahn c'est vraiment mineur , mais si vous vous souciez de ces détails, vous pouvez intervert les 2 booléens dans la boucle: while condition or first_pass:. Puis conditionest toujours évalué en premier et globalement first_passn'est évalué que deux fois (première et dernière itération). N'oubliez pas d'initialiser conditionavant la boucle ce que vous voulez.
pLOPeGG
HM, intéressant, j'avais en fait choisi l'inverse à dessein pour ne pas avoir à initialiser la condition et donc à nécessiter des modifications minimes du code. Cela dit, je vois votre point
evan54
33

L'exception rompra la boucle, vous pourriez donc aussi bien la gérer en dehors de la boucle.

try:
  while True:
    if s:
      print s
    s = i.next()
except StopIteration:   
  pass

Je suppose que le problème avec votre code est que le comportement de l' breakintérieur exceptn'est pas défini. breakNe monte généralement que d'un niveau, donc par exemple, l' breakintérieur tryva directement finally(s'il existe) à une trysortie de la boucle, mais pas à une sortie de boucle.

PEP connexe: http://www.python.org/dev/peps/pep-3136
Question connexe: rupture des boucles imbriquées

vartec
la source
8
C'est une bonne pratique cependant de n'avoir à l'intérieur de l'instruction try que ce que vous attendez de votre exception, de peur que vous ne capturiez des exceptions indésirables.
Paggas
7
@PiPeep: RTFM, recherchez EAFP.
vartec
2
@PiPeep: pas de problème, gardez juste à l'esprit que ce qui est vrai pour certaines langues peut ne pas être vrai pour d'autres. Python est optimisé pour une utilisation intensive des exceptions.
vartec du
5
break et continue sont parfaitement bien définis dans n'importe quelle clause d'une instruction try / except / finally. Ils les ignorent simplement et se détachent ou passent à la prochaine itération de la boucle while ou for contenant, selon le cas. En tant que composants des constructions en boucle, ils ne sont pertinents que pour les instructions while et for et déclenchent une erreur de syntaxe s'ils s'exécutent dans une instruction class ou def avant d'atteindre la boucle la plus interne. Ils ignorent les instructions if, with et try.
ncoghlan
1
.. ce qui est un cas important
javadba
33
do {
  stuff()
} while (condition())

->

while True:
  stuff()
  if not condition():
    break

Vous pouvez faire une fonction:

def do_while(stuff, condition):
  while condition(stuff()):
    pass

Mais 1) c'est moche. 2) La condition devrait être une fonction avec un paramètre, censée être remplie par des trucs (c'est la seule raison de ne pas utiliser la boucle while classique.)

ZeD
la source
5
L'écriture while True: stuff(); if not condition(): breakest une très bonne idée. Je vous remercie!
Noctis Skytower
2
@ZeD, pourquoi est-ce 1) moche? C'est tout à fait OK, à mon humble avis
Sergey Lossev
@SergeyLossev Il va être difficile de saisir la logique du programme, car il apparaît comme une boucle infinie au début, si vous avez beaucoup de code "truc" entre les deux.
exique
16

Voici une solution plus folle d'un modèle différent - en utilisant des coroutines. Le code est toujours très similaire, mais avec une différence importante; il n'y a aucune condition de sortie! La coroutine (chaîne de coroutines vraiment) s'arrête juste lorsque vous arrêtez de la nourrir avec des données.

def coroutine(func):
    """Coroutine decorator

    Coroutines must be started, advanced to their first "yield" point,
    and this decorator does this automatically.
    """
    def startcr(*ar, **kw):
        cr = func(*ar, **kw)
        cr.next()
        return cr
    return startcr

@coroutine
def collector(storage):
    """Act as "sink" and collect all sent in @storage"""
    while True:
        storage.append((yield))

@coroutine      
def state_machine(sink):
    """ .send() new parts to be tokenized by the state machine,
    tokens are passed on to @sink
    """ 
    s = ""
    state = STATE_CODE
    while True: 
        if state is STATE_CODE :
            if "//" in s :
                sink.send((TOKEN_COMMENT, s.split( "//" )[1] ))
                state = STATE_COMMENT
            else :
                sink.send(( TOKEN_CODE, s ))
        if state is STATE_COMMENT :
            if "//" in s :
                sink.send(( TOKEN_COMMENT, s.split( "//" )[1] ))
            else
                state = STATE_CODE
                # re-evaluate same line
                continue
        s = (yield)

tokens = []
sm = state_machine(collector(tokens))
for piece in i:
    sm.send(piece)

Le code ci-dessus recueille tous les jetons sous forme de tuples dans tokenset je suppose qu'il n'y a aucune différence entre .append()et .add()dans le code d'origine.

u0b34a0f6ae
la source
4
Comment écririez-vous cela en Python 3.x aujourd'hui?
Noctis Skytower
14

La façon dont je l'ai fait est la suivante ...

condition = True
while condition:
     do_stuff()
     condition = (<something that evaluates to True or False>)

Cela me semble être la solution simpliste, je suis surpris de ne pas l'avoir déjà vu ici. Cela peut évidemment aussi être inversé pour

while not condition:

etc.

Écluse de Gareth
la source
Vous dites "je suis surpris de ne pas l'avoir déjà vu ici" - mais je ne vois aucune différence par rapport, disons, à la solution de powderflask de 2010. C'est exactement la même chose. ("condition = True tandis que condition: # corps de boucle ici condition = test_loop_condition () # fin de boucle")
cslotty
10

pour une boucle do - while contenant des instructions try

loop = True
while loop:
    generic_stuff()
    try:
        questionable_stuff()
#       to break from successful completion
#       loop = False  
    except:
        optional_stuff()
#       to break from unsuccessful completion - 
#       the case referenced in the OP's question
        loop = False
   finally:
        more_generic_stuff()

alternativement, quand il n'y a pas besoin de la clause «enfin»

while True:
    generic_stuff()
    try:
        questionable_stuff()
#       to break from successful completion
#       break  
    except:
        optional_stuff()
#       to break from unsuccessful completion - 
#       the case referenced in the OP's question
        break
marque
la source
7
while condition is True: 
  stuff()
else:
  stuff()
MuSheng
la source
8
Ew. Cela semble beaucoup plus laid que d'utiliser une pause.
mattdm
5
C'est intelligent, mais cela nécessite stuffd'être une fonction ou de répéter le corps du code.
Noctis Skytower
12
Tout ce qui est nécessaire, c'est while condition:parce que is Truec'est implicite.
martineau
2
cela échoue si cela conditiondépend d'une variable interne de stuff(), car cette variable n'est pas définie à ce moment.
yo '25
5
Pas la même logique, car à la dernière itération quand condition! = True: il appelle le code une dernière fois. Where as a Do While , appelle d'abord le code, puis vérifie la condition avant de réexécuter. Faire pendant: exécuter le bloc une fois; puis vérifiez et réexécutez cette réponse: vérifiez et réexécutez; puis exécutez le bloc de code une fois . Grande différence!
Zv_oDD
7

Piratage rapide:

def dowhile(func = None, condition = None):
    if not func or not condition:
        return
    else:
        func()
        while condition():
            func()

Utilisez comme ça:

>>> x = 10
>>> def f():
...     global x
...     x = x - 1
>>> def c():
        global x
        return x > 0
>>> dowhile(f, c)
>>> print x
0
Naftuli Kay
la source
3

Pourquoi ne fais-tu pas

for s in l :
    print s
print "done"

?

Martin
la source
1
j'ai besoin de créer une machine d'état. Dans la machine d'état, il est normal de réévaluer l'instruction CURRENT, j'ai donc besoin de «continuer» sans itérer l'élément suivant. Je ne sais pas comment faire une telle chose dans 'for s in l:' itération :(. Dans la boucle do-while, 'continue' réévaluera l'élément actuel, itération à la fin.
grigoryvp
alors, pouvez-vous définir un pseudo-code pour votre machine d'état, afin que nous puissions vous suggérer la meilleure solution pythonique? Je ne connais pas grand-chose aux machines à états (et je ne suis probablement pas le seul), donc si vous nous parlez un peu de votre algorithme, il nous sera plus facile de vous aider.
Martin
La boucle For ne fonctionne pas pour des choses comme: a = fun () tandis que a == 'zxc': sleep (10) a = fun ()
harry
Cela rate complètement le point de vérifier une condition booléenne
javadba
1

Voyez si cela aide:

Définissez un indicateur à l'intérieur du gestionnaire d'exceptions et vérifiez-le avant de travailler sur le s.

flagBreak = false;
while True :

    if flagBreak : break

    if s :
        print s
    try :
        s = i.next()
    except StopIteration :
        flagBreak = true

print "done"
Nrj
la source
3
Pourrait être simplifié en utilisant while not flagBreak:et en supprimant le if (flagBreak) : break.
martineau
1
J'évite les variables nommées flag- Je ne peux pas déduire ce que signifient une valeur vraie ou une valeur fausse. Utilisez plutôt doneou endOfIteration. Le code se transforme en while not done: ....
IceArdor
1

Si vous êtes dans un scénario où vous effectuez une boucle alors qu'une ressource est indisponible ou quelque chose de similaire qui lève une exception, vous pouvez utiliser quelque chose comme

import time

while True:
    try:
       f = open('some/path', 'r')
    except IOError:
       print('File could not be read. Retrying in 5 seconds')   
       time.sleep(5)
    else:
       break
Ajit
la source
0

Pour moi, une boucle while typique sera quelque chose comme ceci:

xBool = True
# A counter to force a condition (eg. yCount = some integer value)

while xBool:
    # set up the condition (eg. if yCount > 0):
        (Do something)
        yCount = yCount - 1
    else:
        # (condition is not met, set xBool False)
        xBool = False

Je pourrais également inclure un for..loop dans la boucle while, si la situation le justifie, pour parcourir une autre série de conditions.

user12379095
la source