Emular um loop do-while em Python?

799

Eu preciso emular um loop do-while em um programa Python. Infelizmente, o seguinte código simples não 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"

Em vez de "1,2,3, done", ele imprime a seguinte saída:

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

O que posso fazer para capturar a exceção 'stop iteration' e interromper um loop while corretamente?

Um exemplo de por que tal coisa pode ser necessária é mostrado abaixo como pseudocódigo.

Máquina de estado:

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
fonte
4
Hum ... Isso não é um bom "fazer enquanto"; isso é simplesmente um "fazer para sempre". O que há de errado com "while True" e "break"?
S.Lott
70
S. Lott: Tenho certeza de que a pergunta dele era sobre como implementar o fazer enquanto estiver em python. Portanto, eu não esperaria que o código dele estivesse completamente correto. Além disso, ele está muito perto de fazer um tempo ... ele está verificando uma condição no final do ciclo "para sempre" para ver se deve sair. Não é "fazer para sempre".
Tom
4
então ... seu código de exemplo inicial realmente funciona para mim sem problemas e não recebo esse rastreio. esse é um idioma apropriado para um loop do while, em que a condição de interrupção é a exaustão do iterador. normalmente, você definiria em s=i.next()vez de Nenhum e possivelmente faria algum trabalho inicial, em vez de apenas tornar inútil sua primeira passagem pelo loop.
o encaixe
3
@underrun Infelizmente, o post não está marcado com a versão do Python que está sendo usada - o snippet original também funciona para mim usando o 2.7, provavelmente devido a atualizações na própria linguagem Python.
Hannele

Respostas:

985

Não tenho certeza do que você está tentando fazer. Você pode implementar um loop do-while como este:

while True:
  stuff()
  if fail_condition:
    break

Ou:

stuff()
while not fail_condition:
  stuff()

O que você está tentando usar um loop do while para imprimir as coisas da lista? Por que não usar:

for i in l:
  print i
print "done"

Atualizar:

Então você tem uma lista de linhas? E você quer continuar iterando? E se:

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

Parece algo próximo do que você gostaria? Com o seu exemplo de código, seria:

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
fonte
1
Eu preciso criar uma máquina de estado. Na máquina de estado, é um caso normal reavaliar a instrução CURRENT, portanto, preciso 'continuar' sem iterar o próximo item. Eu não sei como fazer isso em 'for s in l:' iteração :(. No loop do-while, 'continue' reavaliará o item atual, iteração no final
grigoryvp
Quer dizer que você precisa acompanhar o seu lugar na lista? Dessa forma, quando você retornar o mesmo estado, poderá retomar de onde parou? Dê um pouco mais de contexto. Parece que é melhor você usar um índice na lista.
Tom
Obrigado, comentei seu pseudocódigo ... seu exemplo parece meio ruim, pois você parece manipular "//" da mesma maneira, não importa em que estado você esteja. Além disso, esse código é real no qual você está processando comentários? E se você tiver cordas com barras? ie: print "blá // <- isso te atrapalha?"
Tom
4
É uma pena que o python não tenha um loop do-while. Python é SECO, não é?
Kr0e
43
Veja também o PEP 315 para a postura / justificativa oficial: "Os usuários do idioma são aconselhados a usar a forma while-True com um if-break interno quando um loop do-while for apropriado."
dtk
311

Aqui está uma maneira muito simples de emular um loop do-while:

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

Os principais recursos de um loop do-while são que o corpo do loop sempre é executado pelo menos uma vez e que a condição é avaliada na parte inferior do corpo do loop. A estrutura de controle mostrada aqui realiza ambos sem a necessidade de exceções ou instruções de interrupção. Ele introduz uma variável booleana extra.

powderflask
fonte
11
Nem sempre adiciona uma variável booleana extra. Muitas vezes, existem algo (s) que já existem cujo estado pode ser testado.
martineau
14
O motivo pelo qual eu mais gosto dessa solução é que ela não adiciona outra condição, ainda é apenas um ciclo, e se você escolher um bom nome para a variável auxiliar, toda a estrutura será bastante clara.
10263 Roberto
4
NOTA: Embora isso atenda à pergunta original, essa abordagem é menos flexível do que usar break. Especificamente, se houver lógica necessária DEPOIS test_loop_condition(), que não deva ser executada depois que terminarmos, ela deverá ser envolvida if condition:. Aliás, conditioné vago. Mais descritivo: moreou notDone.
Página
7
@ToolmakerSteve Eu discordo. Eu raramente uso breakem loops e, quando o encontro em código que mantenho, acho que o loop, na maioria das vezes, poderia ter sido escrito sem ele. A solução apresentada é, IMO, a maneira mais clara de representar um fazer enquanto constrói em python.
Nonsensickle
1
Idealmente, a condição será nomeada como algo descritivo, como has_no_errorsou end_reached(nesse caso, o loop seria iniciadowhile not end_reached
Josiah Yoder 28/15
75

Meu código abaixo pode ser uma implementação útil, destacando a principal diferença entre vs Como eu entendo.

Portanto, neste caso, você sempre passa pelo loop pelo menos uma vez.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()
evan54
fonte
2
Resposta correta, eu vou argumentar. Além disso, evita quebras , para uso seguro em blocos try / except.
Zv_oDD 26/02
o jit / otimizador evita testar novamente first_pass após a primeira passagem? caso contrário, seria um, embora talvez menor, problema de desempenho irritante
markhahn
2
@markhahn isso é realmente menor, mas se você se importa de tais detalhes, você pode intervert os 2 booleans no loop: while condition or first_pass:. Então conditioné sempre avaliado primeiro e o geral first_passé avaliado apenas duas vezes (primeira e última iteração). Não se esqueça de inicializar conditionantes do loop o que quiser.
pLOPeGG
HM, interessante, na verdade, eu tinha escolhido o contrário propositadamente para não precisar inicializar a condição e, portanto, exigindo alterações mínimas no código. Dito isto eu vejo o seu ponto
evan54
33

A exceção interromperá o loop, então você também pode lidar com isso fora do loop.

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

Eu acho que o problema com o seu código é que o comportamento de breakdentro exceptnão está definido. Geralmente breaksobe apenas um nível, por exemplo, o breakinterior tryvai diretamente para finally(se existir) um fora do tryloop, mas não fora do loop.

PEP relacionado: http://www.python.org/dev/peps/pep-3136
Pergunta relacionada: Rompendo loops aninhados

vartec
fonte
8
No entanto, é uma boa prática ter apenas dentro da instrução try o que você espera lançar sua exceção, para que você não possa capturar exceções indesejadas.
Paggas 02/11/2009
7
@PiPeep: RTFM, procure EAFP.
Vartec
2
@ PiPeep: não há problema, lembre-se de que o que é verdade para alguns idiomas pode não ser verdadeiro para outros. Python é otimizado para uso intensivo de exceções.
vartec
5
break e continue estão perfeitamente bem definidos em qualquer cláusula de uma instrução try / except / finalmente. Eles simplesmente os ignoram e interrompem ou passam para a próxima iteração do loop while ou for, conforme apropriado. Como componentes das construções de loop, eles são relevantes apenas para instruções while e for, e acionam um erro de sintaxe se forem executados em uma classe ou declaração def antes de atingir o loop mais interno. Eles ignoram as declarações if, with e try.
Ncoghlan
1
.. que é um caso importante
javadba 21/03/19
33
do {
  stuff()
} while (condition())

->

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

Você pode fazer uma função:

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

Mas 1) é feio. 2) Condição deve ser uma função com um parâmetro, que deve ser preenchida por coisas (é a única razão para não usar o loop while clássico).

ZeD
fonte
5
Escrever while True: stuff(); if not condition(): breaké uma ideia muito boa. Obrigado!
Noctis Skytower
2
@ZeD, por que 1) é feio? Está tudo bem, IMHO
Sergey Lossev 07/01
@SergeyLossev Vai ser difícil entender a lógica do programa porque ele aparece como um loop infinito no início, se você tiver um monte de código 'material' no meio.
exic 29/04/19
16

Aqui está uma solução mais louca de um padrão diferente - usando corotinas. O código ainda é muito semelhante, mas com uma diferença importante; não há condições de saída! A corotina (cadeia de corotinas realmente) apenas para quando você para de alimentá-la com dados.

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)

O código acima coleta todos os tokens como tuplas tokense presumo que não haja diferença entre .append()e .add()no código original.

u0b34a0f6ae
fonte
4
Como você escreveria isso no Python 3.x hoje?
Noctis Skytower
14

A maneira que eu fiz isso é a seguinte ...

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

Parece-me a solução simplista, estou surpreso por ainda não o ter visto aqui. Obviamente, isso também pode ser invertido para

while not condition:

etc.

Gareth Lock
fonte
Você diz: "Estou surpreso por ainda não ter visto aqui" - mas não vejo nenhuma diferença da, digamos, solução da powderflask de 2010. É exatamente o mesmo. ( "condição = True enquanto condição: # circuito corpo aqui condição = test_loop_condition () # fim de ciclo")
cslotty
10

para um loop do - while contendo instruções 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, quando não há necessidade da 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
fonte
7
while condition is True: 
  stuff()
else:
  stuff()
MuSheng
fonte
8
Ai credo. Isso parece significativamente mais feio do que usar uma pausa.
mattdm
5
Isso é inteligente, mas requer stuffque seja uma função ou que o corpo do código seja repetido.
Noctis Skytower
12
Tudo o que é necessário é while condition:porque is Trueestá implícito.
martineau
2
isso falha se conditiondepender de alguma variável interna de stuff(), porque essa variável não está definida naquele momento.
yo
5
Não é a mesma lógica, porque na última iteração quando condition! = True: chama o código pela última vez. Onde como fazer , chama o código uma vez primeiro e depois verifica a condição antes de executar novamente. Do While: execute o bloco uma vez; depois verifique e execute novamente , esta resposta: verifique e execute novamente; depois execute o bloco de código uma vez . Grande diferença!
Zv_oDD 26/02
7

Corte rápido:

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

Use assim:

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

Por que você não faz

for s in l :
    print s
print "done"

?

Martin
fonte
1
Eu preciso criar uma máquina de estado. Na máquina de estado, é um caso normal reavaliar a instrução CURRENT, portanto, preciso 'continuar' sem iterar o próximo item. Eu não sei como fazer tal coisa em 'para s em l:'.. Iteração :( Em não-while, 'continuar' vai reavaliar item atual, iteração no final
grigoryvp
então, você pode definir algum pseudocódigo para sua máquina de estado, para que possamos sugerir a melhor solução python? Não sei muito sobre máquinas de estado (e provavelmente não sou a única); portanto, se você nos contar um pouco sobre seu algoritmo, será mais fácil ajudá-lo.
Martin Martin
Para loop não funciona para coisas como: a = fun (), enquanto a == 'zxc': sono (10) a = fun ()
harry
Este perde completamente o ponto de verificar uma condição boolean
javadba
1

Veja se isso ajuda:

Defina um sinalizador dentro do manipulador de exceções e verifique-o antes de trabalhar no s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"
Nrj
fonte
3
Pode ser simplificado usando while not flagBreak:e removendo o if (flagBreak) : break.
martineau
1
flagEvito variáveis ​​nomeadas --Não consigo inferir o que um valor Verdadeiro ou Falso significa. Em vez disso, use doneou endOfIteration. O código se transforma while not done: ....
IceArdor 11/03/14
1

Se você estiver em um cenário em que está fazendo um loop enquanto um recurso não está disponível ou é algo semelhante que gera uma exceção, você pode 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
fonte
0

Para mim, um loop while típico será algo como isto:

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

Eu também poderia incluir um loop for..l no loop while, se a situação o justificar, por loop através de outro conjunto de condições.

user12379095
fonte