¿Emular un bucle do-while en Python?

799

Necesito emular un ciclo do-while en un programa Python. Desafortunadamente, el siguiente código directo no funciona:

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"

En lugar de "1,2,3, hecho", imprime el siguiente resultado:

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

¿Qué puedo hacer para detectar la excepción 'detener iteración' y romper un bucle while correctamente?

Un ejemplo de por qué tal cosa puede ser necesaria se muestra a continuación como pseudocódigo.

Máquina estatal:

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
fuente
44
Um ... Eso no es un "tiempo de espera" adecuado; eso es simplemente un "hacer para siempre". ¿Qué hay de malo con "while True" y "break"?
S.Lott
70
S. Lott: Estoy bastante seguro de que su pregunta era sobre cómo implementar do mientras estaba en Python. Por lo tanto, no esperaría que su código sea completamente correcto. Además, está muy cerca de hacer algo mientras ... está comprobando una condición al final del ciclo "para siempre" para ver si debería salir. No es "hacer para siempre".
Tom
44
entonces ... su código de ejemplo inicial realmente funciona para mí sin ningún problema y no obtengo ese rastreo. ese es un idioma apropiado para un ciclo do while donde la condición de interrupción es agotamiento del iterador. normalmente, establecería en s=i.next()lugar de Ninguno y posiblemente haría un trabajo inicial en lugar de simplemente hacer que su primer paso por el bucle sea inútil.
Underrun
3
@underrun Desafortunadamente, la publicación no está etiquetada con la versión de Python que se estaba utilizando; el fragmento original también funciona para mí usando 2.7, presumiblemente debido a las actualizaciones del lenguaje Python.
Hannele

Respuestas:

985

No estoy seguro de lo que estás tratando de hacer. Puede implementar un ciclo do-while como este:

while True:
  stuff()
  if fail_condition:
    break

O:

stuff()
while not fail_condition:
  stuff()

¿Qué estás haciendo tratando de usar un bucle do while para imprimir las cosas en la lista? ¿Por qué no solo usar:

for i in l:
  print i
print "done"

Actualizar:

Entonces, ¿tienes una lista de líneas? ¿Y quieres seguir iterando? Qué tal si:

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

¿Te parece algo parecido a lo que quieres? Con su ejemplo de código, sería:

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
Tom
fuente
1
Necesito crear una máquina de estado. En la máquina de estado, es un caso normal reevaluar la declaración ACTUAL, por lo que necesito 'continuar' sin repetir el siguiente elemento. No sé cómo hacer tal cosa en 'for s in l:' iteration :(. En el ciclo do-while, 'continue' volverá a evaluar el elemento actual, la iteración al final
grigoryvp
¿Quiere decir que necesita hacer un seguimiento de su lugar en la lista? De esa manera, cuando regrese al mismo estado, ¿puede continuar donde lo dejó? Dale un poco más de contexto. Parece que sería mejor usar un índice en la lista.
Tom
Gracias, comenté su pseudocódigo ... su ejemplo parece un poco malo ya que parece manejar "//" de la misma manera sin importar en qué estado se encuentre. Además, ¿es este código real donde está procesando comentarios? ¿Qué pasa si tienes cuerdas con barras? es decir: imprimir "blah // <- ¿eso te confunde?
Tom
44
Es una pena que Python no tenga un ciclo do-while. Python está SECO, ¿eh?
Kr0e
43
También vea PEP 315 para la postura / justificación oficial: "Se aconseja a los usuarios del lenguaje que usen la forma while-True con un if-break interno cuando un ciclo do-while hubiera sido apropiado".
dtk
311

Aquí hay una manera muy simple de emular un ciclo do-while:

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

Las características clave de un ciclo do-while son que el cuerpo del ciclo siempre se ejecuta al menos una vez, y que la condición se evalúa en la parte inferior del cuerpo del ciclo. La estructura de control que se muestra aquí cumple ambas cosas sin necesidad de excepciones o declaraciones de interrupción. Introduce una variable booleana adicional.

frasco de polvo
fuente
11
No siempre agrega una variable booleana adicional. A menudo hay algo (s) que ya existe cuyo estado puede ser probado.
Martineau
14
La razón por la que más me gusta esta solución es que no agrega otra condición, todavía es solo un ciclo, y si elige un buen nombre para la variable auxiliar, toda la estructura está bastante clara.
Roberto
44
NOTA: Si bien esto aborda la pregunta original, este enfoque es menos flexible que el uso break. Específicamente, si se necesita lógica DESPUÉS test_loop_condition(), que no debe ejecutarse una vez que hayamos terminado, debe envolverse if condition:. Por cierto, conditiones vago. Más descriptivo: moreo notDone.
ToolmakerSteve
77
@ToolmakerSteve No estoy de acuerdo. Raramente lo uso breaken bucles y cuando lo encuentro en el código que mantengo, encuentro que el bucle, con mayor frecuencia, podría haberse escrito sin él. La solución presentada es, en mi opinión, la forma más clara de representar un do while build en python.
nonsensickle
1
Idealmente, la condición se denominará algo descriptivo, como has_no_errorso end_reached(en cuyo caso el ciclo comenzaríawhile not end_reached
Josiah Yoder
75

Mi código a continuación podría ser una implementación útil, destacando la principal diferencia entre vs como yo lo entiendo

Entonces, en este caso, siempre pasa por el ciclo al menos una vez.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()
evan54
fuente
2
Respuesta correcta, discutiré. Además, evita roturas , para un uso seguro en bloques try / except.
Zv_oDD
¿el jit / optimizer evita volver a probar first_pass después del primer pase? de lo contrario, sería un problema de rendimiento molesto, aunque quizás menor,
markhahn
2
@markhahn esto es realmente menor, pero si usted se preocupa de tales detalles, puede intervert los 2 booleanos en el bucle: while condition or first_pass:. Luego, conditionsiempre se evalúa primero y en general first_passse evalúa solo dos veces (primera y última iteración). No olvides inicializar conditionantes del ciclo a lo que quieras.
pLOPeGG
HM, interesante, en realidad había elegido al revés a propósito para no tener que inicializar la condición y, por lo tanto, requería cambios mínimos en el código. Dicho esto, veo tu punto
evan54
33

La excepción romperá el ciclo, por lo que también podría manejarlo fuera del ciclo.

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

Supongo que el problema con su código es que el comportamiento del breakinterior exceptno está definido. Por lo general, breaksolo sube un nivel, por ejemplo, el breakinterior tryva directamente a finally(si existe) un fuera del tryciclo, pero no fuera del ciclo.

PEP relacionada: http://www.python.org/dev/peps/pep-3136
Pregunta relacionada: ruptura de bucles anidados

vartec
fuente
8
Sin embargo, es una buena práctica tener dentro de la declaración de prueba lo que espera lanzar su excepción, para no atrapar excepciones no deseadas.
Paggas el
77
@PiPeep: RTFM, busque EAFP.
vartec el
2
@PiPeep: no hay problema, solo tenga en cuenta que lo que es cierto para algunos idiomas, puede no ser cierto para otros. Python está optimizado para el uso intensivo de excepciones.
Vartec
55
break y continue están perfectamente bien definidos en cualquier cláusula de una declaración try / except / finally Simplemente los ignoran, y salen o pasan a la siguiente iteración del contenedor while o for loop según corresponda. Como componentes de las construcciones de bucle, solo son relevantes para las declaraciones while y for, y desencadenan un error de sintaxis si se encuentran con una declaración de clase o def antes de llegar al bucle más interno. Ignoran las declaraciones if, with y try.
ncoghlan
1
.. que es un caso importante
javadba
33
do {
  stuff()
} while (condition())

->

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

Puedes hacer una función:

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

Pero 1) es feo. 2) La condición debe ser una función con un parámetro, que se supone que debe rellenarse con cosas (es la única razón para no usar el clásico bucle while).

ZeD
fuente
55
Escribir while True: stuff(); if not condition(): breakes una muy buena idea. ¡Gracias!
Noctis Skytower
2
@ZeD, ¿por qué es 1) feo? Está bastante bien, en mi humilde opinión
Sergey Lossev
@SergeyLossev Va a ser difícil comprender la lógica del programa porque al principio aparece como un bucle infinito, si tiene un montón de código 'intermedio'.
exic
16

Aquí hay una solución más loca de un patrón diferente: el uso de corutinas. El código sigue siendo muy similar, pero con una diferencia importante; no hay condiciones de salida en absoluto! La corutina (cadena de corutinas realmente) simplemente se detiene cuando deja de alimentarla con datos.

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)

El código anterior recopila todos los tokens como tuplas tokensy supongo que no hay diferencia entre .append()y .add()en el código original.

u0b34a0f6ae
fuente
44
¿Cómo escribirías esto en Python 3.x hoy?
Noctis Skytower
14

La forma en que hice esto es la siguiente ...

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

Esto me parece la solución simplista, me sorprende no haberlo visto aquí. Obviamente, esto también se puede invertir a

while not condition:

etc.

Gareth Lock
fuente
Usted dice "Estoy sorprendido de que no lo haya visto aquí", pero no veo ninguna diferencia con respecto a, digamos, la solución de frasco en polvo de 2010. Es exactamente lo mismo. ("condición = Verdadero mientras condición: # cuerpo del bucle aquí condición = test_loop_condition () # fin del ciclo")
cslotty
10

para un bucle do - while que contiene sentencias 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()

alternativamente, cuando no hay necesidad de la cláusula 'finalmente'

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
marca
fuente
7
while condition is True: 
  stuff()
else:
  stuff()
MuSheng
fuente
8
Ew. Eso parece significativamente más feo que usar un descanso.
mattdm
55
Eso es inteligente, pero requiere stuffser una función o que el cuerpo del código se repita.
Noctis Skytower
12
Todo lo que se necesita es while condition:porque is Trueestá implícito.
Martineau
2
esto falla si conditiondepende de alguna variable interna de stuff(), porque esa variable no está definida en ese momento.
yo'
55
No es la misma lógica, porque en la última iteración cuando condición! = Verdadero: Llama al código por última vez. Where as a Do While , llama al código una vez primero, luego verifica la condición antes de volver a ejecutarlo. Do While: ejecuta el bloque una vez; luego verifique y vuelva a ejecutar , esta respuesta: verifique y vuelva a ejecutar; luego ejecute el bloque de código una vez . ¡Gran diferencia!
Zv_oDD
7

Hack rápido:

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

Use así:

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

¿Por qué no lo haces?

for s in l :
    print s
print "done"

?

Martín
fuente
1
Necesito crear una máquina de estado. En la máquina de estado, es un caso normal reevaluar la declaración ACTUAL, por lo que necesito 'continuar' sin repetir el siguiente elemento. No sé cómo hacer tal cosa en 'for s in l:' iteration :(. En el ciclo do-while, 'continue' volverá a evaluar el elemento actual, la iteración al final.
grigoryvp
entonces, ¿puede definir algún pseudocódigo para su máquina de estado, para que podamos darle una pista sobre la mejor solución pitónica? No sé mucho acerca de las máquinas de estado (y probablemente no soy el único), así que si nos cuenta un poco sobre su algoritmo, será más fácil para nosotros ayudarlo.
Martin
For loop no funciona para cosas como: a = fun () mientras a == 'zxc': sleep (10) a = fun ()
harry
Esto pierde completamente el punto de verificar una condición booleana
javadba
1

Vea si esto ayuda:

Establezca un indicador dentro del controlador de excepciones y verifíquelo antes de trabajar en el s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"
Nrj
fuente
3
Podría simplificarse mediante el uso while not flagBreak:y la eliminación de if (flagBreak) : break.
Martineau
1
flagEvito las variables nombradas: no puedo inferir lo que significa un valor verdadero o un valor falso. En su lugar, use doneo endOfIteration. El código se convierte en while not done: ....
IceArdor
1

Si se encuentra en un escenario en el que realiza un bucle mientras un recurso no está disponible o algo similar que arroja una excepción, podría usar algo como

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
fuente
0

Para mí, un bucle while típico será algo como esto:

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

Podría incluir un bucle for..loop dentro del bucle while también, si la situación lo amerita, para recorrer otro conjunto de condiciones.

usuario12379095
fuente