Pedirle al usuario una entrada hasta que den una respuesta válida

Estoy escribiendo un progtwig que debe aceptar la entrada del usuario.

#note: Python 2.7 users should use `raw_input`, the equivalent of 3.X's `input` age = int(input("Please enter your age: ")) if age >= 18: print("You are able to vote in the United States!") else: print("You are not able to vote in the United States.") 

Esto funciona como se espera si el usuario ingresa datos sensibles.

 C:\Python\Projects> canyouvote.py Please enter your age: 23 You are able to vote in the United States! 

Pero si cometen un error, entonces se estrella:

 C:\Python\Projects> canyouvote.py Please enter your age: dickety six Traceback (most recent call last): File "canyouvote.py", line 1, in  age = int(input("Please enter your age: ")) ValueError: invalid literal for int() with base 10: 'dickety six' 

En lugar de estrellarme, me gustaría que intentara obtener la información nuevamente. Me gusta esto:

 C:\Python\Projects> canyouvote.py Please enter your age: dickety six Sorry, I didn't understand that. Please enter your age: 26 You are able to vote in the United States! 

¿Cómo puedo lograr esto? ¿Y si también quisiera rechazar valores como -1 , que es un int válido, pero no tiene sentido en este contexto?

La forma más sencilla de lograr esto sería poner el método de input en un bucle while. Use continue cuando tenga mala entrada y salga del ciclo cuando esté satisfecho.

Cuando su entrada puede generar una excepción

Utilice try and catch para detectar cuándo el usuario ingresa datos que no se pueden analizar.

 while True: try: # Note: Python 2.x users should use raw_input, the equivalent of 3.x's input age = int(input("Please enter your age: ")) except ValueError: print("Sorry, I didn't understand that.") #better try again... Return to the start of the loop continue else: #age was successfully parsed! #we're ready to exit the loop. break if age >= 18: print("You are able to vote in the United States!") else: print("You are not able to vote in the United States.") 

Implementando tus propias reglas de validación

Si desea rechazar valores que Python pueda analizar con éxito, puede agregar su propia lógica de validación.

 while True: data = input("Please enter a loud message (must be all caps): ") if not data.isupper(): print("Sorry, your response was not loud enough.") continue else: #we're happy with the value given. #we're ready to exit the loop. break while True: data = input("Pick an answer from A to D:") if data.lower() not in ('a', 'b', 'c', 'd'): print("Not an appropriate choice.") else: break 

Combinación de manejo de excepciones y validación personalizada

Ambas técnicas anteriores se pueden combinar en un bucle.

 while True: try: age = int(input("Please enter your age: ")) except ValueError: print("Sorry, I didn't understand that.") continue if age < 0: print("Sorry, your response must not be negative.") continue else: #age was successfully parsed, and we're happy with its value. #we're ready to exit the loop. break if age >= 18: print("You are able to vote in the United States!") else: print("You are not able to vote in the United States.") 

Encapsulando todo en una función

Si necesita pedirle a su usuario muchos valores diferentes, puede ser útil colocar este código en una función, para que no tenga que volver a escribirlo cada vez.

 def get_non_negative_int(prompt): while True: try: value = int(input(prompt)) except ValueError: print("Sorry, I didn't understand that.") continue if value < 0: print("Sorry, your response must not be negative.") continue else: break return value age = get_non_negative_int("Please enter your age: ") kids = get_non_negative_int("Please enter the number of children you have: ") salary = get_non_negative_int("Please enter your yearly earnings, in dollars: ") 

Poniendolo todo junto

Puedes ampliar esta idea para hacer una función de entrada muy genérica:

 def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None): if min_ is not None and max_ is not None and max_ < min_: raise ValueError("min_ must be less than or equal to max_.") while True: ui = input(prompt) if type_ is not None: try: ui = type_(ui) except ValueError: print("Input type must be {0}.".format(type_.__name__)) continue if max_ is not None and ui > max_: print("Input must be less than or equal to {0}.".format(max_)) elif min_ is not None and ui < min_: print("Input must be greater than or equal to {0}.".format(min_)) elif range_ is not None and ui not in range_: if isinstance(range_, range): template = "Input must be between {0.start} and {0.stop}." print(template.format(range_)) else: template = "Input must be {0}." if len(range_) == 1: print(template.format(*range_)) else: print(template.format(" or ".join((", ".join(map(str, range_[:-1])), str(range_[-1]))))) else: return ui 

Con usos como:

 age = sanitised_input("Enter your age: ", int, 1, 101) answer = sanitised_input("Enter your answer: ", str.lower, range_=('a', 'b', 'c', 'd')) 

Trampas comunes y por qué debe evitarlas

El uso redundante de declaraciones de input redundantes

Este método funciona, pero generalmente se considera estilo pobre:

 data = input("Please enter a loud message (must be all caps): ") while not data.isupper(): print("Sorry, your response was not loud enough.") data = input("Please enter a loud message (must be all caps): ") 

Puede parecer atractivo inicialmente porque es más corto que el método while True , pero viola el principio de No repetirte a ti mismo del desarrollo de software. Esto aumenta la probabilidad de errores en su sistema. ¿Qué raw_input si desea realizar una raw_input de raw_input a 2.7 cambiando la input a raw_input , pero cambiando accidentalmente solo la primera input anterior? Es un SyntaxError está esperando a suceder.

La recursión soplará tu stack

Si acaba de enterarse de la recursión, puede tener la tentación de usarlo en get_non_negative_int para poder deshacerse del bucle while.

 def get_non_negative_int(prompt): try: value = int(input(prompt)) except ValueError: print("Sorry, I didn't understand that.") return get_non_negative_int(prompt) if value < 0: print("Sorry, your response must not be negative.") return get_non_negative_int(prompt) else: return value 

Esto parece funcionar bien la mayor parte del tiempo, pero si el usuario ingresa datos no válidos suficientes veces, la secuencia de comandos terminará con un RuntimeError: maximum recursion depth exceeded . Puedes pensar que "ningún tonto cometerá 1000 errores seguidos", ¡pero estás subestimando el ingenio de los tontos!

¿Por qué haría un while True y luego saldría de este bucle mientras que también puede poner sus requisitos en la sentencia while ya que todo lo que quiere es parar una vez que tenga la edad?

 age = None while age is None: input_value = input("Please enter your age: ") try: # try and convert the string input to a number age = int(input_value) except ValueError: # tell the user off print("{input} is not a number, please enter a number only".format(input=input_value)) if age >= 18: print("You are able to vote in the United States!") else: print("You are not able to vote in the United States.") 

Esto daría lugar a lo siguiente:

 Please enter your age: *potato* potato is not a number, please enter a number only Please enter your age: *5* You are not able to vote in the United States. 

esto funcionará ya que la edad nunca tendrá un valor que no tenga sentido y el código sigue la lógica de su “proceso de negocio”

Aunque la respuesta aceptada es asombrosa. También me gustaría compartir un truco rápido para este problema. (Esto también se ocupa del problema de la edad negativa.)

 f=lambda age: (age.isdigit() and ((int(age)>=18 and "Can vote" ) or "Cannot vote")) or \ f(input("invalid input. Try again\nPlease enter your age: ")) print(f(input("Please enter your age: "))) 

PS Este código es para Python 3.x.

Por lo tanto, estuve jugando con algo similar a esto recientemente, y se me ocurrió la siguiente solución, que utiliza una forma de obtener información que rechaza la basura, incluso antes de que se verifique de alguna manera lógica.

read_single_keypress() cortesía https://stackoverflow.com/a/6599441/4532996

 def read_single_keypress() -> str: """Waits for a single keypress on stdin. -- from :: https://stackoverflow.com/a/6599441/4532996 """ import termios, fcntl, sys, os fd = sys.stdin.fileno() # save old state flags_save = fcntl.fcntl(fd, fcntl.F_GETFL) attrs_save = termios.tcgetattr(fd) # make raw - the way to do this comes from the termios(3) man page. attrs = list(attrs_save) # copy the stored version to update # iflag attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK | termios.ISTRIP | termios.INLCR | termios. IGNCR | termios.ICRNL | termios.IXON ) # oflag attrs[1] &= ~termios.OPOST # cflag attrs[2] &= ~(termios.CSIZE | termios. PARENB) attrs[2] |= termios.CS8 # lflag attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON | termios.ISIG | termios.IEXTEN) termios.tcsetattr(fd, termios.TCSANOW, attrs) # turn off non-blocking fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK) # read a single keystroke try: ret = sys.stdin.read(1) # returns a single character except KeyboardInterrupt: ret = 0 finally: # restre old state termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save) fcntl.fcntl(fd, fcntl.F_SETFL, flags_save) return ret def until_not_multi(chars) -> str: """read stdin until !(chars)""" import sys chars = list(chars) y = "" sys.stdout.flush() while True: i = read_single_keypress() _ = sys.stdout.write(i) sys.stdout.flush() if i not in chars: break y += i return y def _can_you_vote() -> str: """a practical example: test if a user can vote based purely on keypresses""" print("can you vote? age : ", end="") x = int("0" + until_not_multi("0123456789")) if not x: print("\nsorry, age can only consist of digits.") return print("your age is", x, "\nYou can vote!" if x >= 18 else "Sorry! you can't vote") _can_you_vote() 

Puedes encontrar el módulo completo aquí .

Ejemplo:

 $ ./input_constrain.py can you vote? age : a sorry, age can only consist of digits. $ ./input_constrain.py can you vote? age : 23 your age is 23 You can vote! $ _ 

Tenga en cuenta que la naturaleza de esta implementación es que cierra la entrada estándar tan pronto como se lee algo que no es un dígito. No presioné Enter después de a , pero necesitaba hacerlo después de los números.

Podría combinar esto con la función thismany() en el mismo módulo para permitir solo, digamos, tres dígitos.

 def validate_age(age): if age >=0 : return True return False while True: try: age = int(raw_input("Please enter your age:")) if validate_age(age): break except ValueError: print "Error: Invalid age." 

Para editar su código y corregir el error:

 while True: try: age = int(input("Please enter your age: ")) if age >= 18: print("You are able to vote in the United States!") break else: print("You are not able to vote in the United States.") break except ValueError: print("Please enter a valid response") 

Prueba este: –

 def takeInput(required): print 'ooo or OOO to exit' ans = raw_input('Enter: ') if not ans: print "You entered nothing...!" return takeInput(required) ## FOR Exit ## elif ans in ['ooo', 'OOO']: print "Closing instance." exit() else: if ans.isdigit(): current = 'int' elif set('[~!@#$%^&*()_+{}":/\']+$').intersection(ans): current = 'other' elif isinstance(ans,basestring): current = 'str' else: current = 'none' if required == current : return ans else: return takeInput(required) ## pass the value in which type you want [str/int/special character(as other )] print "input: ", takeInput('str')