An innocent question...
There is a very funny thread currently in the PyAr (Python Argentina) mailing list.
It all started when "Galileo Galilei" asked about how to do a very simple thing. He presented this code:
age = int(raw_input('How old are you?')) if age<18: print 'You are underage' else: print 'You are a grownup'
Ok, the original was... not quite as polite, but the code is the same. So far, nothing strange. But then he asked this:
How can I make it so that if the user enters something that is not a number, it does something like this:
or maybe
You can probably imagine that asking this kind of thing should produce maybe two answers. Right?
That is indeed the case, and you can see that in the answer by Facundo Batista or Ezequiel.
Except... what if we wanted it to keep asking in case the user entered not-a-number?
Then, my friends... it's about taste, and it's all Juan Pedro Fisanotti's fault.
Here's my take:
while True: edad=raw_input('¿Cuantos años tenes?') if edad.isdigit(): break print 'No ingresaste un numero!'
Yes, I admit, a bit old fashioned. And there was a cry of "no, break sucks, it's not right", which leads to this by Manuel Aráoz
age = raw_input('Your age?') while not age.isdigit(): print "That's not a number!" age = raw_input('Your age?')
Which caused cries of "Having raw_input twice is ugly!", which leads to (again by Manuel Aráoz):
get_age = lambda: raw_input('Your age?') age = get_age() while not age.isdigit(): print 'Not a number!' age = get_age()
Here Patricio Molina digs up PEP 315.
And then Alejandro Santos says something like "This is easier in C, because we can assign a value to age in the while's condition". Please remember this.
Now Pablo Zilliani gives his version, which is, I must say, perfect in some ways:
age = reset = msg = 'Age?: ' while not age.isdigit(): age = raw_input(msg) msg = "%r is not a number!, %s" % (age, reset) print age
Here Gabriel Genellina decides to defend break by hitting everyone in the head using Knuth which should have a much stronger effect than Hitler.
And now, we start veering into weird waters. Here is what news proposes, which I must say, I admire... from a respectful distance.
First, the relevant code:
edad = "0" # Entra igual la primera vez while firstTrue (not edad.isdigit()): edad = raw_input ("¿Cuantos años tenes? ") if not edad.isdigit(): print "No ingresaste un nro!"
But what, exactly, is firstTrue?
import inspect def firstTrue(cond): """ devuelve True siempre la primera vez que se la ejecuta, las veces subsiguientes evalua la condicion """ stack = inspect.stack()[1] # El stack del programa llamador line = stack[2] # Nro de linea desde la que llame a firstTrue del stack if not "line" in firstTrue.__dict__: # Primera vez que llamo a la funcion firstTrue.line = line return True elif firstTrue.line != line: # Llame a la funcion desde otro punto del programa firstTrue.line = line return True return cond
Then, I bring up generators, which leads to Claudio Freire's, which almost works, too:
age = '' def invalidAge(): yield True while not age.isdigit(): print "Not a number" yield True yield False for i in invalidAge(): age = raw_input("Age please: ") print age
And then Fabian Gallina is the second one to bring up C's assignments inside conditions.
You know, I can't accept that. I will not accept C being easier for this.
So, with a little help from the python cookbook...
age=[1] while not age |asig| raw_input('Age? '): print 'Not a number!' print u'You are %s years old'%age[0]
You may ask, what's |asig|
? Glad you asked!
class Infix: def __init__(self, function): self.function = function def __ror__(self, other): return Infix(lambda x, self=self, other=other: self.function(other, x)) def __or__(self, other): return self.function(other) def __rlshift__(self, other): return Infix(lambda x, self=self, other=other: self.function(other, x)) def __rshift__(self, other): return self.function(other) def __call__(self, value1, value2): return self.function(value1, value2) def opasigna (x,y): x[0]=y return y.isdigit() asig=Infix(opasigna)
And then, Pablo posts this gem:
import inspect def assign(var, value): stack = inspect.stack()[1][0] stack.f_locals [var] = value del stack return value while not assign("age", raw_input('Age? ')).isdigit(): print u'Not a number!' print u'You are %s years old' % age
Which is, IMVHO, about as far from trivial as you can get here. Of course the thread is not dead yet ;-)
try/exception ?
age = None
while age is None:
try:
age = int(raw_input("Age? ")
group = age < 13 and "underage" or "a grownup"
print "You are %s" % group
except ValueError:
print "Sorry, I need a number."
I didn't read the thread, so I don't know whether someone came up with a itertools solution. Here's mine:
from itertools import dropwhile, count
age = dropwhile(lambda s: not s.isdigit(), (raw_input() for _ in count())).next()
(My solution basically says: drop all inputs while they aren't digits, and then keep the next one)
Me gusta, but it doesn't print anything when you fail to input a number.
I'll just mention that there is an easier way to say
(raw_input() for _ in count())
that would be
iter(raw_input, None)
Nice tip, rgz, ¡gracias!
If there were a hypotetical dropuntil function, and using py3k's next() and input(), my improved solution would be:
age = next(dropuntil(str.isdigit, iter(input, None)))
which I find nice, even when it doesn't print the required message :)
Saludos.
What? No use of the WITH statement yet?!?!?!?!
Come on, that thing was MADE for stuff like this ;-)
This is why python needs a goto command.
I agree with whoever defended break. It exists just for this reason. This discussion reminds me of the junior C-programmers who think that "goto" is forbidden. Even in the context of escaping nested loops, where it is clearly the correct solution.
Anywho.. Did anyone suggest a recursive solution?
def getGoodAge():
__age = raw_input('Your age?')
____if age.isdigit(): return age
____else:
______print 'screw you'
______return getGoodAge()
No duplication.
You welcome Roberto.
On a related note I have found out that about 75% of my while statements in python are "while True:" and the some goes for most third party code I see.
I really think python should have dropped while in favor of a "loop:" construct, after all even in a while you have to pay attention to breaks and continues
th of , in a recent project a php programmer ask me what was the equivalent of:
while($row = mysql_fetch_row($resource)){}
I was about to suggest code duplication or writing a generator to drink the rows when I figured out about using iter(cursor.fetchone, None), thankfully cursor.fetchone returns None after exhaustion so it works
I hate with-hacks, I always prefer decorators instead of with-haks,
OO solution
class Questioner:
..def __init__(
....self,m1, m2, m3
....prompt,
Whole thing as a recursive solution.
def agecheck( age = None ):
try:
return "You are underage" if int(age) < 18 else "You are a grownup"
except ValueError:
error_response = "[ Invalid age ] "
except TypeError:
error_response = ""
return agecheck( raw_input( "%show old are you?" % error_response ) )
print agecheck()
age = None
while not age or not age.isdigit():
....print "That's not a number!"
....age = raw_input('Your age?')
ops, sorry wrong paste, that's it:
age = None
while not age or not age.isdigit():
....if age:
........print "That's not a number!"
....age = raw_input('Your age?')
Also wrong paste, last part of my last comment was supposed to be deleted.
The recursive solution really is the most elegant... if you want to avoid break, but that's silly, break is good.
Does python need a goto? I don't think so, breaks with arguments sounds like a better solution. If python had a goto I at least would hope you have to land it inside a designated goto scope, something like:
with goto:
....while foo:
........while bar:
............if baz:
................goto exit
............else:
................goto get_lost
....exit:
........print "can goto here"
get_lost:
....print "can't goto here"
David Fendrich wins.
I was being (mostly) sarcastic about the goto. It wouldn't fit with python.
But at the same time, I think this pseudo C code is far more readable than any of the proposed python-without-break solutions:
ask_age:
age = raw_input("Your age?");
if (!isdigit(age)) {
printf("That's not a number!");
goto ask_age;
}
That's the missing third while of C. Wasn't it Knutt himself who said that there where three kinds of while loops:
while condition{code;}
do{code;}while(condition)
and finally
do{code;}while(condition){more code;}
Whoever wrote that also stated that the third while was a superset of the former two and that it was superior.
Which is why I think
loop:
...
if condition: break
...
Is actually the ideal. But I woudn't even bother suggesting that in the mailing list.
@rgz indeed that was Knuth, and it was even mentioned in the original thread :-)
Yep, I tend to go with a solution like michele's -- either initialise a variable to None and loop until it's right, or if None is one of the valid values, use an additional done flag, looping on while not done: ...
Just to avoid break? It's not worth it.
Nice post! Thank you.. :)
The most "pythonic" solution I can think of is this:
while True:
....try:
........age = int(raw_input("Enter your age: "))
........break
....except ValueError:
........print "You must enter a number."
There is a related task on Rosetta Code, here: http://rosettacode.org/wiki....
It contains solutions coded in many languages including Python.
The text of the RC task follows:
Given a list containing a number of strings of which one is to be selected and a prompt string, create a function that:
* Print a textual menu formatted as an index value followed by its corresponding string for each item in the list.
* Prompt the user to enter a number.
* return the string corresponding to the index number.
The function should reject input that is not an integer or is an out of range integer index by recreating the whole menu before asking again for a number. The function should return an empty string if called with an empty list.
For test purposes use the four phrases: 'fee fie', 'huff and puff', 'mirror mirror' and 'tick tock' in a list.
rgz: more to avoid the inelegance of while True. To me, while True is pretty horrible -- it reads as "forever", but doesn't actually work out that way.
With a 'done' variable, you're just naming the state that 'if' is checking for anyway, and a good optimiser should probably be able to reduce it to a register use or even directly down to a conditional jump. So, does what it says, without being inefficient, and is fairly straightforward.
I do think this is a limitation in python that should be addressed though. I'm starting to lean towards a generic helper function that takes a block or lambda... a bit like Roberto's dropwhile, but more self-explanatory.
I liked the sound of your loop: construct, rgz, but I'm not sure what you're getting at. Hopefully not a goto sort of thing ;)