Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Una pregunta inocente...

To­do em­pe­zó cuan­do "Ga­li­leo Ga­li­lei" pre­gun­tó co­mo ha­cer una co­sa muy sim­ple. Él mos­tró es­te có­di­go:

age = int(raw_input('Cual es tu edad?'))
if age<18:
    print 'Sos menor'
else:
    print 'Sos mayor'

En rea­li­dad el ori­gi­na­l... era un po­co más gua­ran­go, pe­ro el có­di­go es bá­si­ca­men­te el mis­mo. has­ta ahí na­da ra­ro. Pe­ro en­ton­ces pre­gun­tó es­to:

Có­mo pue­do ha­cer que si el usua­rio en­tra al­go que no es un nú­me­ro, ha­ga al­go ti­po:

print 'No tengo poderes de supervaca'

o

print 'Error de tipeo'

Uno se ima­gi­na­ría que esa cla­se de pre­gun­ta pue­de pro­du­cir una o dos res­pues­ta­s. ¿No?

Efec­ti­va­men­te así es, y se ven las res­pues­tas de Fa­cun­do Ba­tis­ta o Eze­quiel.

Pe­ro­... que pa­sa­ría si que­re­mos se­guir pre­gun­tan­do cuan­do el usua­rio en­tra un no­-­nú­me­ro?

En­ton­ces ami­go­s... es una cues­tión de gus­to, y es to­do cul­pa de Juan Pe­dro Fi­sano­tti.

Acá es­tá mi idea:

while True:
    edad=raw_input('¿Cuantos años tenes?')
    if edad.isdigit():
        break
    print 'No ingresaste un numero!'

Sí, lo ad­mi­to, un po­co a la an­ti­gua. Y hu­bu gri­tos de "no, break es una por­que­ría, no es­tá bien", lo que lle­va a és­to, de Ma­nuel Aráoz

age = raw_input('Tu edad?')
while not age.isdigit():
    print "No es un número!"
    age = raw_input('Tu edad?')

Lo que lle­va a llan­tos de "Te­ner dos raw_i­n­put es feo­!", lo que a su vez pro­vo­ca es­to (nue­va­men­te, Ma­nuel 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á Pa­tri­cio Mo­li­na pe­la la PEP 315.

Y en­ton­ces Ale­jan­dro San­tos di­ce al­go co­mo "Es­to es más fa­cil en C por­que po­de­mos asig­nar va­lo­res a edad en la con­di­ción del whi­le". Acuér­den­se de es­to.

Aho­ra Pa­blo Zi­llia­ni da su ver­sió­n, que, de­bo de­ci­r, es per­fec­ta en cier­ta for­ma:

age = reset = msg = 'Edad?: '
while not age.isdigit():
    age = raw_input(msg)
    msg = "%r no es un numero!, %s" % (age, reset)

print age

En­ton­ces Ga­briel Ge­ne­lli­na de­ci­de de­fen­der el uso de break pe­gán­do­nos a to­dos en la ca­be­za con Knu­th lo que de­be­ría te­ner un efec­to mu­cho más po­ten­te que men­cio­nar a Hi­tle­r.

Y va­mos lle­gan­do a aguas po­co na­ve­ga­ble­s. Acá es­tá la pro­pues­ta de news , que ad­mi­ro. A una res­pe­tuo­sa dis­tan­cia.

Pri­me­ro el có­di­go re­le­van­te:

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

¿Pe­ro qué, exac­ta­men­te, es firs­tTrue?

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

En­ton­ces yo men­ciono ge­ne­ra­do­res, lo que lle­va a es­to, por Clau­dio Frei­re, que ca­si fun­cio­na:

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 en­ton­ces Fa­bian Ga­lli­na es el se­gun­do en men­cio­nar que en C po­dés asig­nar en con­di­cio­nes.

No pien­so acep­tar­lo. C no pue­de ser más fá­cil pa­ra es­to!

Así que con una ayu­di­ta del cook­book...

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 en­ton­ce­s, Pa­blo pos­tea es­ta jo­yi­ta:

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 pa­re­ce a mí, lo me­nos tri­vial que se pue­de lle­gar con es­te pro­ble­ma. Cla­ro que el hi­lo no se mu­rió to­da­vía ;-)

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