Skip to main content

Ralsina.Me — Roberto Alsina's website

An innocent question...

There is a very fun­ny thread cur­rent­ly in the PyAr (Python Ar­genti­na) mail­ing list.

It all start­ed when "Galileo Galilei" asked about how to do a very sim­ple thing. He pre­sent­ed 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 orig­i­nal was... not quite as po­lite, but the code is the same. So far, noth­ing strange. But then he asked this:

How can I make it so that if the us­er en­ters some­thing that is not a num­ber, it does some­thing like this:

print 'I have no supercow powers'

or maybe

print 'Typing error'

You can prob­a­bly imag­ine that ask­ing this kind of thing should pro­duce maybe two an­swer­s. Right?

That is in­deed the case, and you can see that in the an­swer by Fa­cun­do Batista or Eze­quiel.

Ex­cep­t... what if we want­ed it to keep ask­ing in case the us­er en­tered not-a-num­ber?

Then, my friend­s... it's about taste, and it's all Juan Pe­dro Fisan­ot­ti'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 ad­mit, a bit old fash­ioned. And there was a cry of "no, break suck­s, 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 "Hav­ing raw_in­put twice is ug­ly!", which leads to (a­gain 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 Patri­cio Moli­na digs up PEP 315.

And then Ale­jan­dro San­tos says some­thing like "This is eas­i­er in C, be­cause we can as­sign a val­ue to age in the while's con­di­tion". Please re­mem­ber this.

Now Pablo Zil­liani gives his ver­sion, which is, I must say, per­fect 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 Genel­li­na de­cides to de­fend break by hit­ting ev­ery­one in the head us­ing Knuth which should have a much stronger ef­fect than Hitler.

And now, we start veer­ing in­to weird wa­ter­s. Here is what news pro­pos­es, which I must say, I ad­mire... from a re­spect­ful dis­tance.

First, the rel­e­vant 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, ex­act­ly, is first­True?

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 gen­er­a­tors, which leads to Clau­dio Freire's, which al­most work­s, 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 Fabi­an Gal­li­na is the sec­ond one to bring up C's as­sign­ments in­side con­di­tion­s.

You know, I can't ac­cept that. I will not ac­cept C be­ing eas­i­er for this.

So, with a lit­tle help from the python cook­book...

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 triv­ial as you can get here. Of course the thread is not dead yet ;-)

Damien / 2009-09-18 01:43:

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."

Roberto Bonvallet / 2009-09-18 02:14:

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()

Roberto Bonvallet / 2009-09-18 02:16:

(My solution basically says: drop all inputs while they aren't digits, and then keep the next one)

rgz / 2009-09-18 02:33:

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)

Roberto Bonvallet / 2009-09-18 03:33:

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.

Doug Napoleone / 2009-09-18 03:46:

What? No use of the WITH statement yet?!?!?!?!

Come on, that thing was MADE for stuff like this ;-)

Matthew Marshall / 2009-09-18 03:48:

This is why python needs a goto command.

David Fendrich / 2009-09-18 08:12:

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.

rgz / 2009-09-18 08:37:

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,

Bernice W / 2009-09-18 10:48:

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()

michele / 2009-09-18 13:07:

age = None
while not age or not age.isdigit():
....print "That's not a number!"
....age = raw_input('Your age?')

michele / 2009-09-18 13:09:

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?')

rgz / 2009-09-18 16:39:

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.

Matthew Marshall / 2009-09-18 21:23:

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;
}

rgz / 2009-09-18 22:10:

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.

Roberto Alsina / 2009-09-19 00:46:

@rgz indeed that was Knuth, and it was even mentioned in the original thread :-)

Lee / 2009-09-19 01:45:

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: ...

rgz / 2009-09-19 02:45:

Just to avoid break? It's not worth it.

Alex Dedul / 2009-09-19 07:04:

Nice post! Thank you.. :)

John / 2009-09-19 08:07:

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."

Paddy3118 / 2009-09-19 08:22:

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.

Lee / 2009-09-19 09:42:

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 ;)


Contents © 2000-2023 Roberto Alsina