Una pregunta inocente...
Todo empezó cuando "Galileo Galilei" preguntó como hacer una cosa muy simple. Él mostró este código:
En realidad el original... era un poco más guarango, pero el código es básicamente el mismo. hasta ahí nada raro. Pero entonces preguntó esto:
Cómo puedo hacer que si el usuario entra algo que no es un número, haga algo tipo:
o
Uno se imaginaría que esa clase de pregunta puede producir una o dos respuestas. ¿No?
Efectivamente así es, y se ven las respuestas de Facundo Batista o Ezequiel.
Pero... que pasaría si queremos seguir preguntando cuando el usuario entra un no-número?
Entonces amigos... es una cuestión de gusto, y es todo culpa de Juan Pedro Fisanotti.
Acá está mi idea:
while True: edad=raw_input('¿Cuantos años tenes?') if edad.isdigit(): break print 'No ingresaste un numero!'
Sí, lo admito, un poco a la antigua. Y hubu gritos de "no, break es una porquería, no está bien", lo que lleva a ésto, de Manuel Aráoz
age = raw_input('Tu edad?') while not age.isdigit(): print "No es un número!" age = raw_input('Tu edad?')
Lo que lleva a llantos de "Tener dos raw_input es feo!", lo que a su vez provoca esto (nuevamente, Manuel Aráoz:
get_age = lambda: raw_input('Tu edad?') age = get_age() while not age.isdigit(): print 'No es un numero!' age = get_age()
Acá Patricio Molina pela la PEP 315.
Y entonces Alejandro Santos dice algo como "Esto es más facil en C porque podemos asignar valores a edad en la condición del while". Acuérdense de esto.
Ahora Pablo Zilliani da su versión, que, debo decir, es perfecta en cierta forma:
age = reset = msg = 'Edad?: ' while not age.isdigit(): age = raw_input(msg) msg = "%r no es un numero!, %s" % (age, reset) print age
Entonces Gabriel Genellina decide defender el uso de break pegándonos a todos en la cabeza con Knuth lo que debería tener un efecto mucho más potente que mencionar a Hitler.
Y vamos llegando a aguas poco navegables. Acá está la propuesta de news , que admiro. A una respetuosa distancia.
Primero el código relevante:
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!"
¿Pero qué, exactamente, es 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
Entonces yo menciono generadores, lo que lleva a esto, por Claudio Freire, que casi funciona:
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
Y entonces Fabian Gallina es el segundo en mencionar que en C podés asignar en condiciones.
No pienso aceptarlo. C no puede ser más fácil para esto!
Así que con una ayudita del cookbook...
edad=[1] while not edad |asig| raw_input('Edad? '): print u'Poné un número!' print u'Tenes %s años'%edad[0]
¿Pero qué es |asig|
? ¡Qué buena pregunta!
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)
Y entonces, Pablo postea esta joyita:
import inspect def assign(var, value): stack = inspect.stack()[1][0] stack.f_locals [var] = value del stack return value while not assign("edad", raw_input('Edad? ')).isdigit(): print u'No es un numero!' print u'Tenes %s años' % edad
Que es, me parece a mí, lo menos trivial que se puede llegar con este problema. Claro que el hilo no se murió todavía ;-)
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 ;)