12

In the C programming language, I often have done the following:

while ((c = getch()) != EOF) {
 /* do something with c */
}

In Python, I have not found anything similar, since I am not allowed to set variables inside the evaluated expression. I usually end up with having to setup the evaluated expression twice!

c = sys.stdin.read(1)
while not (c == EOF):
 # Do something with c
 c = sys.stdin.read(1)

In my attempts to find a better way, I've found a way that only require to setup and the evaluated expression once, but this is getting uglier...

while True:
 c = sys.stdin.read(1)
 if (c == EOF): break
 # do stuff with c

So far I've settled with the following method for some of my cases, but this is far from optimal for the regular while loops...:

class ConditionalFileObjectReader:
 def __init__(self,fobj, filterfunc):
  self.filterfunc = filterfunc
  self.fobj = fobj
 def __iter__(self):
  return self
 def next(self):
  c = self.fobj.read(1)
  if self.filterfunc(c): raise StopIteration
  return c

for c in ConditionalFileObjectReader(sys.stdin,lambda c: c == EOF):
 print c

All my solutions to solve a simple basic programming problem has become too complex... Do anyone have a suggestion how to do this the proper way?

nicael
  • 18,550
  • 13
  • 57
  • 90
  • 1
    I found this: http://www.python.org/dev/peps/pep-0315/ But sadly, it has not been implemented yet... – Dog eat cat world Jun 21 '11 at 14:57
  • Pretty similar to this question: http://stackoverflow.com/questions/2941272/small-code-redundancy-within-while-loops-doesnt-feel-clean – Ponkadoodle Jun 21 '11 at 15:35
  • You might be interested in my answer to [do…while vs while](http://stackoverflow.com/q/3347001/188535) — a bit tangential, but still relevant. – detly Jun 21 '11 at 15:39

3 Answers3

18

You would usually use a for loop in Python:

for c in sys.stdin.read():
    # whatever

If you don't want to buffer the whole stdin in memory at once, you can also add some buffering with a smaller buffer yourself.

Note that the constant EOF does not exist in Python. read() will simply return an empty string if no data is left in the stream.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Great, but this does not solve the basic uglyness of while loops in python. Reading from stdin was just meant as en example where this "problem" often occur. – Dog eat cat world Jun 21 '11 at 14:48
  • Meanwhile, `input()` and the like won't return `EOF` but will instead raise an `EOFError` if `EOF` is encountered before any other data. – JAB Jun 21 '11 at 14:49
  • 4
    @Dog eat cat world: Then don't use `while` loops for things that don't need `while` loops in Python. – JAB Jun 21 '11 at 14:50
  • it addresses the fact that you don't need them too often. a LOT of things are iterable in python, and you can put a for loop on any of them – jon_darkstar Jun 21 '11 at 14:50
  • @Dog eat cat world: That gets really subjective. Not to sound like an old veteran or something, but I believe once you've learned a dozen languages under your belt you stop caring about "ugliness" or what not. – kizzx2 Jun 21 '11 at 14:50
  • 9
    @Dog: `while` loops are much less common in Python than in C. And personally I don't consider a break condition in the middle of the loop ugly -- they also often occur in C. – Sven Marnach Jun 21 '11 at 14:51
  • @Sven Marnach, I dont consider the break ugly, but that you move the evaluated expression away from the while syntax. – Dog eat cat world Jun 21 '11 at 14:55
  • 1
    It's not just a question of buffering the whole input in memory at once, `read` blocks until sufficient data is available or EOF. Are we saying for certain that Python has *no* way to write a loop for streaming I/O that's better than `while True ... break`? Which personally I can live with, but I see why people might prefer a "cleaner" alternative. – Steve Jessop Jun 21 '11 at 15:00
  • 2
    @Dog you didn't ask for a solution to "the basic ugliness of while loops in python". You asked for "the proper way". There's a reason you don't often see while loops in Python: the reason is because they're so rarely the one preferably obvious right way to do it. – kojiro Jun 21 '11 at 16:28
14

I believe what you want to do is make use of the iter function.

for c in iter(getch, EOF):
     #inner loop

Iter is a very versatile function. In this case, you're telling it to repeatedly call getch (with no arguments) at the top of each loop until getch returns the sentinel value, EOF.

Ponkadoodle
  • 5,777
  • 5
  • 38
  • 62
  • 1
    Thanks, this also works. I replaced getch with _lambda: sys.stdin.read(1)_ But this question was meant for the general while syntax ugliness, and not bound to this specific problem. But I see the point of using iterators when possible :) – Dog eat cat world Jun 21 '11 at 15:40
  • 1
    @Brendan: It's `functools.partial()`, and I don't think it would be faster (didn't test). – Sven Marnach Jun 21 '11 at 22:11
  • Oops. I was under the impression that lambas are fairly slow in python, so `partial` might be better. – Brendan Long Jun 21 '11 at 23:06
3

It's possible to write much simpler code in place of your ConditionalFileObjectReader, considering that EOF seems to be what you care about, rather than any arbitrary condition:

def readbytes(file):
    while True:
        c = file.read(1)
        if c == '':
            return
        yield c

for c in readbytes(sys.stdin):
    print c

So you still have 'while True ... break', which seems to be the preferred loop in Python[*], but at least you only have it once to solve the whole class of problem, "how to iterate over the bytes in a file-like object without blocking/buffering each line", and you have it in a short loop that doesn't "do stuff with c" - that's a separate concern.

Inspired by Wallacoloo's example with iter, similar to the above you could produce something more general than iter:

def until(nextvalue, pred):
    while True:
        value = nextvalue()
        if pred(value):
            return
        yield value

for c in until(lambda: sys.stdin.read(1), lambda x: x == ''):
    print c

I'm not sure whether I like this or not, but might be worth playing with. It tries to solve the general problem "iterate over the return values of some function, until a return value satisfies some condition".

[*] dare I say, the Pythonic equivalent of fancy loop syntax in other languages?

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699