views:

182

answers:

1

So, in Python (though I think it can be applied to many languages), I find myself with something like this quite often:

the_input = raw_input("what to print?\n")
while the_input != "quit":
    print the_input
    the_input = raw_input("what to print?\n")

Maybe I'm being too picky, but I don't like how the line the_input = raw_input("what to print?\n") has to get repeated. It decreases maintainability and organization. But I don't see any workarounds for avoiding the duplicate code without further worsening the problem. In some languages, I could write something like this:

while ((the_input=raw_input("what to print?\n")) != "quit") {
    print the_input
}

This is definitely not Pythonic, and Python doesn't even allow for assignment within loop conditions AFAIK.

This valid code fixes the redundancy,

while 1:
    the_input = raw_input("what to print?\n")
    if the_input == "quit":
        break
    print the_input

But doesn't feel quite right either. The while 1 implies that this loop will run forever; I'm using a loop, but giving it a fake condition and putting the real one inside it.

Am I being too picky? Is there a better way to do this? Perhaps there's some language construct designed for this that I don't know of?

+26  A: 

Think iterators -- e.g., in this specific case:

for the_input in iter(lambda: raw_input('what to print?\n'), 'quit'):
    print the_input

Most loops in Python, except at the very lowest levels of abstractions, are best implemented as for loops with the help of some underling iterator which captures the "looping logic" -- the iter built-in can help (like here), sometimes genexps (generator expressions) can, sometimes the standard library module itertools comes to the rescue.

Most often you will choose to code custom generator functions (ones using yield), or more occasionally (when you need really sophisticated state management) a custom iterator class (one defining the __iter__ special method as return self, and next [[or __next__ in the latest versions of Python]] to return "the next value from the iteration).

Capturing the looping logic apart from whatever it is that you do on the various items sequentially produced by the loop itself is the key abstraction-helper here!

Alex Martelli
That feels just a bit too clever...
Robert Harvey
+1: Prefer `for`-loops for iteration. In any language that provides them.
Johnsyweb
This is an amazing solution. SO won't let me accept an answer for another 4 minutes though ;-)
Wallacoloo
@Robert, why "too clever"? It's **the** best way to do application-level loops in Python -- sequester the looping logic (how do you initiate a loop's next leg and decide when the loop is done) away from the application logic of "what do you **do** with the results of each leg of the loop". Built-ins like `iter`, standard library modules like `itertools`, entire language constructs like generators and genexps, exist essentially to support this crucial style choice which you deem "too clever"! Care to expand on your pithy condemnation...?
Alex Martelli
@wallacoloo, that's OK, I'll wait patiently for 4 more minutes;-).
Alex Martelli
I **do** like the use of iter(callable, sentinel) (didn't know about that before), but I don't really like the use of the lambda (though I don't really expect you to define a function for a small example like this) (I would just like the look of "for line in iter(prompt, 'quit'):")
Terence Honles
(and by *you* I am referring to Alex...)
Terence Honles
@Terence, I'm no lambda-fan either, but two-arg `iter` needs an argument-less callable as its first item and `lambda` is the most concise way to make one, esp. when no logic is needed, just some trivial adaptation, like here (functools.partial is an alternative, but [[beyond the fact it's nowhere as concise, sigh]] I feel hostile to it ever since it cravenly avoided the proper name for that functionality, "currying"!-).
Alex Martelli