193

Is there a

do until x:
    ...

in Python, or a nice way to implement such a looping construct?

Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526

4 Answers4

320

There is no do-while loop in Python.

This is a similar construct, taken from the link above.

 while True:
     do_something()
     if condition():
        break
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
theycallmemorty
  • 12,515
  • 14
  • 51
  • 71
  • 42
    BTW, this is called "loop-and-a-half". Python continues to support this construct because it's one of the easiest loop patterns to correctly write and understand. See http://www.cs.duke.edu/~ola/patterns/plopd/loops.html#loop-and-a-half – Brandon Nov 02 '09 at 19:05
  • 4
    @Brandon Is this any different than: `while !condition do_something()` ? – Bort Nov 18 '16 at 03:24
  • 16
    @Bort The loop-and-a-half construct guarantees that `do_something()` will be executed at least once, even if `condition()` is true at the beginning of the loop. Your `while not condition(): do_something()` construct will never execute `do_something()` if `condition()` evaluates to true at the beginning. – Brandon Nov 30 '16 at 04:43
  • 7
    @brandon yes, the "easy, natural way" is the loop-and-a-half(TM) way, especially because the [just-one-fscking-]until way is so much harder to grasp than "infinite loop with break" ... the truly pythonic, PEP-excreting way. PS: the only reason until isn't in Python is because they found no sane way to incorporate it in the forcefully-indented syntax (at least functional languages compensate this with tail-recursion). – Miloslav Raus Aug 07 '17 at 20:47
  • 1
    I would lean towards what @Bort is getting at, and if you want to guarantee a first run then `do_something()` before the while loop. It's not DRY, but I think it's most readable. `do_something() while condition do_something()`. The article posted by @Brandon is calling this a `sentinel loop`. At the end of the day it's a stylistic choice left to linters, codebase consistency, and/or team choice. Everytime I see a `while True` I fear it never ends, which is why I like to see a condition set with the while line, even if it's not DRY; I don't want to hunt for a break or digest a crazy logic tree – CTS_AE Dec 01 '19 at 10:59
  • DRY is not about readbility it's about maintainability. - repeated code is often more readable than refactored code – Jasen Dec 04 '19 at 22:23
  • @CTS_AE You may have a false sense of security if you think that `while condition` mean the loop will end. There's no guarantee that `condition` will ever be true. At least with `while True` you know that either A) this loop is meant to run forever, or B) there's a break/return somewhere in the body. You can usually intuit which will be the case based on context. – Sean Sep 15 '21 at 22:18
51

I prefer to use a looping variable, as it tends to read a bit nicer than just "while 1:", and no ugly-looking break statement:

finished = False
while not finished:
    ... do something...
    finished = evaluate_end_condition()
PaulMcG
  • 62,419
  • 16
  • 94
  • 130
29

There's no prepackaged "do-while", but the general Python way to implement peculiar looping constructs is through generators and other iterators, e.g.:

import itertools

def dowhile(predicate):
  it = itertools.repeat(None)
  for _ in it:
    yield
    if not predicate(): break

so, for example:

i=7; j=3
for _ in dowhile(lambda: i<j):
  print i, j
  i+=1; j-=1

executes one leg, as desired, even though the predicate's already false at the start.

It's normally better to encapsulate more of the looping logic into your generator (or other iterator) -- for example, if you often have cases where one variable increases, one decreases, and you need a do/while loop comparing them, you could code:

def incandec(i, j, delta=1):
  while True:
    yield i, j
    if j <= i: break
    i+=delta; j-=delta

which you can use like:

for i, j in incandec(i=7, j=3):
  print i, j

It's up to you how much loop-related logic you want to put inside your generator (or other iterator) and how much you want to have outside of it (just like for any other use of a function, class, or other mechanism you can use to refactor code out of your main stream of execution), but, generally speaking, I like to see the generator used in a for loop that has little (ideally none) "loop control logic" (code related to updating state variables for the next loop leg and/or making tests about whether you should be looping again or not).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 4
    You could use [itertools.takewhile](https://docs.python.org/2/library/itertools.html#itertools.takewhile). – Peter Wood Sep 17 '14 at 10:01
  • Note that takewhile also consumes the first element in the sequence/generator that does not satisfy the predicate function - that's how it knows to stop taking. But if you then want to iterate over the rest thinking "now I'll get everything where the predicate was False", you'll miss the first item of those. – PaulMcG Jul 18 '19 at 19:04
12

No there isn't. Instead use a while loop such as:

while 1:
 ...statements...
  if cond:
    break
Adrian Toman
  • 11,316
  • 5
  • 48
  • 62
f0b0s
  • 2,978
  • 26
  • 30
  • 12
    Why `while 1`? What's wrong with `while True`? Why force a conversion from int to bool? – S.Lott Nov 02 '09 at 18:10
  • 12
    @S.Lott, actually, in Python 2.X, True/False are not keywords, they are just built in global constants (that are reassignable like any other variable), so the interpreter has to check what they point to. See http://stackoverflow.com/a/3815387/311220 – Acorn Feb 06 '12 at 18:05
  • 5
    "the interpreter has to check what they point to"? What? Are you claiming this is some kind of useful optimization? If so, do you have `timeit` benchmarks to substantiate that claim? – S.Lott Feb 06 '12 at 18:08
  • 14
    Python 2.7.3 $ python -mtimeit 'while 0:pass' 100000000 loops, best of 3: 0.0132 usec per loop $ python -mtimeit 'while False:pass' 10000000 loops, best of 3: 0.0538 usec per loop – yingted Dec 24 '12 at 17:04
  • 3
    @yingted "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil." -Donald Knuth Obvious over insignificantly faster, I say. – jpmc26 Jan 24 '14 at 23:17
  • 9
    @jpmc26 I'm just backing up the claim that `0` and `1` are faster than `False` and `True`. For the other 3%, it helps to know that (perhaps counterintuitively) `1` is faster than `True`. – yingted Jan 30 '14 at 17:13
  • 1
    @yingted I know, but the performance gains are so small that if that kind of time actually matters, you would get much better performance by saving the overhead of using an interpreted language like Python and going with C or C++ or something else native. So I cannot think of a use case when this performance improvement is actually a good idea. – jpmc26 Jan 30 '14 at 22:01
  • 14
    @jpmc26 I've used Python for programming contests to cut down on development time. Sometimes, in a hard-to-port solution, a tight numerical loop is the bottleneck, and switching ` True` to `1` bumps my solution from "time limit exceeded" to "correct", a tiny ~10%-20% speed increase. Just because you've never needed an optimization doesn't it doesn't have its uses. – yingted Jan 31 '14 at 22:43
  • All of this says to me we need better metaprogramming so we can make our own do_while, until, etc wrapped around a block of code. I can dream of it anyway. –  Dec 14 '16 at 02:44
  • 2
    Just to note, this is no longer true in Python 3, due to the fact that `False` is a keyword now. – chepner Feb 02 '20 at 14:00
  • 3
    Yes, so just to close out the discussion, python 3.8.2 µbenches: `python -mtimeit "while False: pass;"` 50000000 loops, best of 5: 8.24 nsec per loop `python -mtimeit "while 0: pass;"` 50000000 loops, best of 5: 8.32 nsec per loop – Amit Naidu Aug 31 '20 at 21:58