Solicitar al usuario su aporte hasta que brinde una respuesta válida


562

Estoy escribiendo un programa que acepta una 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.")

El programa funciona según lo esperado siempre que el usuario ingrese datos significativos.

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

Pero falla si el usuario ingresa datos no válidos:

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

En lugar de fallar, me gustaría que el programa vuelva a solicitar la entrada. 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 hacer que el programa solicite entradas válidas en lugar de fallar cuando se ingresan datos no sensibles?

¿Cómo puedo rechazar valores como -1, que es válido int, pero sin sentido en este contexto?

Respuestas:


704

La forma más sencilla de lograr esto es poner el inputmétodo en un ciclo while. Úselo continuecuando obtenga una entrada incorrecta y breakfuera del ciclo cuando esté satisfecho.

Cuando su entrada puede generar una excepción

Use tryyexcept 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 sus propias reglas de validación

Si desea rechazar valores que Python puede 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 solo 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 poner 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

Puede 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:
                    expected = " or ".join((
                        ", ".join(str(x) for x in range_[:-1]),
                        str(range_[-1])
                    ))
                    print(template.format(expected))
        else:
            return ui

Con uso como:

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

Errores comunes y por qué debería evitarlos

El uso redundante de inputdeclaraciones redundantes

Este método funciona pero generalmente se considera un 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 while Truemétodo, pero viola el principio de no repetirse del desarrollo de software. Esto aumenta la probabilidad de errores en su sistema. ¿Qué sucede si desea retroceder a 2.7 al cambiar inputa raw_input, pero cambiar accidentalmente solo el primero de inputarriba? Es SyntaxErrorsolo esperar que suceda.

La recursión volará tu pila

Si acaba de enterarse de la recursividad, puede tener la tentación de usarla get_non_negative_intpara poder deshacerse del ciclo 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, el script terminará con un RuntimeError: maximum recursion depth exceeded. Puedes pensar que "ningún tonto cometería 1000 errores seguidos", ¡pero estás subestimando el ingenio de los tontos!


53
Es divertido leerlo con muchos ejemplos, felicitaciones. Lección subestimada: "¡No subestimes el ingenio de los tontos!"
vpibano

3
De todos modos, no solo habría votado a favor de las preguntas y respuestas, ya que son geniales, sino que sellaron el trato con "dickety six". Bien hecho, @Kevin.
erekalper

1
No estimes el ingenio de los tontos ... y los atacantes inteligentes. Un ataque de DOS sería más fácil para este tipo de cosas, pero otros pueden ser posibles.
Solomon Ucko

¿Podemos usar el nuevo operador "morsa" en lugar de entradas redundantes? ¿Es también un estilo pobre?
J Arun Mani

1
@JArunMani No creo que sea de mal estilo, pero podría ser un poco menos legible. De hecho, solo tendrá uno inputpor ciclo y el ciclo se volverá muy corto, pero la condición podría ser bastante larga ...
Tomerikoo

39

¿Por qué harías un while Truey luego saldrías de este ciclo mientras también puedes poner tus requisitos en la declaración while ya que todo lo que quieres es parar una vez que tienes 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 resultaría en 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 tendrá sentido y el código sigue la lógica de su "proceso comercial"


22

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: ")))

PD Este código es para python 3.x.


1
Tenga en cuenta que este código es recursivo, pero la recursividad no es necesaria aquí y, como dijo Kevin, puede volar su pila.
PM 2Ring

2
@ PM2Ring: tienes razón. Pero mi propósito aquí era solo mostrar cómo el "cortocircuito" puede minimizar (embellecer) piezas largas de código.
aaveg

11
¿Por qué asignarías un lambda a una variable? Solo usa defen su lugar. def f(age):es mucho más claro quef = lambda age:
GP89

3
En algunos casos, es posible que necesite la edad solo una vez y luego no hay uso de esa función. Uno puede querer usar una función y tirarla después de que el trabajo esté terminado. Además, esta puede no ser la mejor manera, pero definitivamente es una forma diferente de hacerlo (que era el propósito de mi solución).
aaveg

@aaveg, ¿cómo convertiría este código para realmente salvar la edad proporcionada por el usuario?
Tytire Recubans

12

Entonces, estaba jugando con algo similar a esto recientemente, y se me ocurrió la siguiente solución, que utiliza una forma de obtener entradas que rechazan la basura, incluso antes de que se verifique de 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:
        # restore 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()

Puede 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<RETURN>
your age is 23
You can vote!
$ _

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

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


12

Enfoque funcional o "¡ mira mamá sin bucles! ":

from itertools import chain, repeat

prompts = chain(["Enter a number: "], repeat("Not a number! Try again: "))
replies = map(input, prompts)
valid_response = next(filter(str.isdigit, replies))
print(valid_response)
Enter a number:  a
Not a number! Try again:  b
Not a number! Try again:  1
1

o si desea tener un mensaje de "entrada incorrecta" separado de una solicitud de entrada como en otras respuestas:

prompt_msg = "Enter a number: "
bad_input_msg = "Sorry, I didn't understand that."
prompts = chain([prompt_msg], repeat('\n'.join([bad_input_msg, prompt_msg])))
replies = map(input, prompts)
valid_response = next(filter(str.isdigit, replies))
print(valid_response)
Enter a number:  a
Sorry, I didn't understand that.
Enter a number:  b
Sorry, I didn't understand that.
Enter a number:  1
1

¿Como funciona?

  1. prompts = chain(["Enter a number: "], repeat("Not a number! Try again: "))
    Esta combinación de itertools.chainy itertools.repeatcreará un iterador que producirá cadenas "Enter a number: "una vez y "Not a number! Try again: "un número infinito de veces:
    for prompt in prompts:
        print(prompt)
    
    Enter a number: 
    Not a number! Try again: 
    Not a number! Try again: 
    Not a number! Try again: 
    # ... and so on
    
  2. replies = map(input, prompts)- aquí mapaplicará todas las promptscadenas del paso anterior a la inputfunción. P.ej:
    for reply in replies:
        print(reply)
    
    Enter a number:  a
    a
    Not a number! Try again:  1
    1
    Not a number! Try again:  it doesn't care now
    it doesn't care now
    # and so on...
    
  3. Usamos filtery str.isdigitfiltramos aquellas cadenas que contienen solo dígitos:
    only_digits = filter(str.isdigit, replies)
    for reply in only_digits:
        print(reply)
    
    Enter a number:  a
    Not a number! Try again:  1
    1
    Not a number! Try again:  2
    2
    Not a number! Try again:  b
    Not a number! Try again: # and so on...
    
    Y para obtener solo la primera cadena de solo dígitos que usamos next.

Otras reglas de validación:

  1. Métodos de cadena: por supuesto, puede usar otros métodos de cadena como str.isalphaobtener solo cadenas alfabéticas o str.isuppersolo mayúsculas. Ver documentos para la lista completa.

  2. Prueba de membresía:
    hay varias formas diferentes de realizarla. Uno de ellos es mediante el __contains__método:

    from itertools import chain, repeat
    
    fruits = {'apple', 'orange', 'peach'}
    prompts = chain(["Enter a fruit: "], repeat("I don't know this one! Try again: "))
    replies = map(input, prompts)
    valid_response = next(filter(fruits.__contains__, replies))
    print(valid_response)
    
    Enter a fruit:  1
    I don't know this one! Try again:  foo
    I don't know this one! Try again:  apple
    apple
    
  3. Comparación de números:
    Existen métodos de comparación útiles que podemos usar aquí. Por ejemplo, para __lt__( <):

    from itertools import chain, repeat
    
    prompts = chain(["Enter a positive number:"], repeat("I need a positive number! Try again:"))
    replies = map(input, prompts)
    numeric_strings = filter(str.isnumeric, replies)
    numbers = map(float, numeric_strings)
    is_positive = (0.).__lt__
    valid_response = next(filter(is_positive, numbers))
    print(valid_response)
    
    Enter a positive number: a
    I need a positive number! Try again: -5
    I need a positive number! Try again: 0
    I need a positive number! Try again: 5
    5.0
    

    O, si no le gusta usar los métodos dunder (dunder = double-subrayado), siempre puede definir su propia función o usar las del operatormódulo.

  4. Existencia de ruta:
    aquí se puede usar la pathlibbiblioteca y su Path.existsmétodo:

    from itertools import chain, repeat
    from pathlib import Path
    
    prompts = chain(["Enter a path: "], repeat("This path doesn't exist! Try again: "))
    replies = map(input, prompts)
    paths = map(Path, replies)
    valid_response = next(filter(Path.exists, paths))
    print(valid_response)
    
    Enter a path:  a b c
    This path doesn't exist! Try again:  1
    This path doesn't exist! Try again:  existing_file.txt
    existing_file.txt
    

Número limitado de intentos:

Si no desea torturar a un usuario preguntándole algo un número infinito de veces, puede especificar un límite en una llamada de itertools.repeat. Esto se puede combinar con proporcionar un valor predeterminado a la nextfunción:

from itertools import chain, repeat

prompts = chain(["Enter a number:"], repeat("Not a number! Try again:", 2))
replies = map(input, prompts)
valid_response = next(filter(str.isdigit, replies), None)
print("You've failed miserably!" if valid_response is None else 'Well done!')
Enter a number: a
Not a number! Try again: b
Not a number! Try again: c
You've failed miserably!

Preprocesamiento de datos de entrada:

A veces no queremos rechazar una entrada si el usuario la suministró accidentalmente EN MAYÚSCULAS o con un espacio al principio o al final de la cadena. Para tener en cuenta estos errores simples, podemos preprocesar los datos de entrada aplicando str.lowery str.stripmétodos. Por ejemplo, para el caso de prueba de membresía, el código se verá así:

from itertools import chain, repeat

fruits = {'apple', 'orange', 'peach'}
prompts = chain(["Enter a fruit: "], repeat("I don't know this one! Try again: "))
replies = map(input, prompts)
lowercased_replies = map(str.lower, replies)
stripped_replies = map(str.strip, lowercased_replies)
valid_response = next(filter(fruits.__contains__, stripped_replies))
print(valid_response)
Enter a fruit:  duck
I don't know this one! Try again:     Orange
orange

En el caso de que tenga muchas funciones para usar para el preprocesamiento, puede ser más fácil usar una función que realice una composición de funciones . Por ejemplo, usando el de aquí :

from itertools import chain, repeat

from lz.functional import compose

fruits = {'apple', 'orange', 'peach'}
prompts = chain(["Enter a fruit: "], repeat("I don't know this one! Try again: "))
replies = map(input, prompts)
process = compose(str.strip, str.lower)  # you can add more functions here
processed_replies = map(process, replies)
valid_response = next(filter(fruits.__contains__, processed_replies))
print(valid_response)
Enter a fruit:  potato
I don't know this one! Try again:   PEACH
peach

Combinación de reglas de validación:

Para un caso simple, por ejemplo, cuando el programa solicita una edad entre 1 y 120, uno puede agregar otro filter:

from itertools import chain, repeat

prompt_msg = "Enter your age (1-120): "
bad_input_msg = "Wrong input."
prompts = chain([prompt_msg], repeat('\n'.join([bad_input_msg, prompt_msg])))
replies = map(input, prompts)
numeric_replies = filter(str.isdigit, replies)
ages = map(int, numeric_replies)
positive_ages = filter((0).__lt__, ages)
not_too_big_ages = filter((120).__ge__, positive_ages)
valid_response = next(not_too_big_ages)
print(valid_response)

Pero en el caso de que haya muchas reglas, es mejor implementar una función que realice una conjunción lógica . En el siguiente ejemplo, usaré uno listo desde aquí :

from functools import partial
from itertools import chain, repeat

from lz.logical import conjoin


def is_one_letter(string: str) -> bool:
    return len(string) == 1


rules = [str.isalpha, str.isupper, is_one_letter, 'C'.__le__, 'P'.__ge__]

prompt_msg = "Enter a letter (C-P): "
bad_input_msg = "Wrong input."
prompts = chain([prompt_msg], repeat('\n'.join([bad_input_msg, prompt_msg])))
replies = map(input, prompts)
valid_response = next(filter(conjoin(*rules), replies))
print(valid_response)
Enter a letter (C-P):  5
Wrong input.
Enter a letter (C-P):  f
Wrong input.
Enter a letter (C-P):  CDE
Wrong input.
Enter a letter (C-P):  Q
Wrong input.
Enter a letter (C-P):  N
N

Desafortunadamente, si alguien necesita un mensaje personalizado para cada caso fallido, me temo que no hay una manera bastante funcional. O, al menos, no pude encontrar uno.


Qué respuesta tan completa y maravillosa, el desglose de la explicación fue genial.
Locane

Usando su estilo, ¿cómo se eliminaría el espacio en blanco y se reduciría la entrada para la prueba de membresía? No quiero crear un conjunto que deba incluir ejemplos en mayúsculas y minúsculas. También me gustaría permitir errores de entrada de espacios en blanco.
Austin

1
@ Austin Agregué una nueva sección sobre preprocesamiento. Echar un vistazo.
Georgy

Eso me recuerda a ReactiveX. ¿Pero quizás eso se inspiró en lenguajes funcionales en primer lugar?
Mateen Ulhaq

8

Usando Click :

Click es una biblioteca para interfaces de línea de comandos y proporciona funcionalidad para solicitar una respuesta válida de un usuario.

Ejemplo simple:

import click

number = click.prompt('Please enter a number', type=float)
print(number)
Please enter a number: 
 a
Error: a is not a valid floating point value
Please enter a number: 
 10
10.0

Tenga en cuenta cómo convirtió el valor de cadena en un flotante automáticamente.

Comprobando si un valor está dentro de un rango:

Se proporcionan diferentes tipos personalizados . Para obtener un número en un rango específico podemos usar IntRange:

age = click.prompt("What's your age?", type=click.IntRange(1, 120))
print(age)
What's your age?: 
 a
Error: a is not a valid integer
What's your age?: 
 0
Error: 0 is not in the valid range of 1 to 120.
What's your age?: 
 5
5

También podemos especificar solo uno de los límites, mino max:

age = click.prompt("What's your age?", type=click.IntRange(min=14))
print(age)
What's your age?: 
 0
Error: 0 is smaller than the minimum valid value 14.
What's your age?: 
 18
18

Prueba de membresía:

Usando click.Choicetipo. Por defecto, esta comprobación distingue entre mayúsculas y minúsculas.

choices = {'apple', 'orange', 'peach'}
choice = click.prompt('Provide a fruit', type=click.Choice(choices, case_sensitive=False))
print(choice)
Provide a fruit (apple, peach, orange): 
 banana
Error: invalid choice: banana. (choose from apple, peach, orange)
Provide a fruit (apple, peach, orange): 
 OrAnGe
orange

Trabajando con rutas y archivos:

Usando un click.Pathtipo podemos verificar las rutas existentes y también resolverlas:

path = click.prompt('Provide path', type=click.Path(exists=True, resolve_path=True))
print(path)
Provide path: 
 nonexistent
Error: Path "nonexistent" does not exist.
Provide path: 
 existing_folder
'/path/to/existing_folder

La lectura y escritura de archivos se puede hacer de la siguiente manera click.File:

file = click.prompt('In which file to write data?', type=click.File('w'))
with file.open():
    file.write('Hello!')
# More info about `lazy=True` at:
# https://click.palletsprojects.com/en/7.x/arguments/#file-opening-safety
file = click.prompt('Which file you wanna read?', type=click.File(lazy=True))
with file.open():
    print(file.read())
In which file to write data?: 
         # <-- provided an empty string, which is an illegal name for a file
In which file to write data?: 
 some_file.txt
Which file you wanna read?: 
 nonexistent.txt
Error: Could not open file: nonexistent.txt: No such file or directory
Which file you wanna read?: 
 some_file.txt
Hello!

Otros ejemplos:

Confirmación de contraseña:

password = click.prompt('Enter password', hide_input=True, confirmation_prompt=True)
print(password)
Enter password: 
 ······
Repeat for confirmation: 
 ·
Error: the two entered values do not match
Enter password: 
 ······
Repeat for confirmation: 
 ······
qwerty

Valores predeterminados:

En este caso, simplemente presionando Enter(o cualquier tecla que use) sin ingresar un valor, le dará uno predeterminado:

number = click.prompt('Please enter a number', type=int, default=42)
print(number)
Please enter a number [42]: 
 a
Error: a is not a valid integer
Please enter a number [42]: 

42

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

2

Sobre la base de las excelentes sugerencias de Daniel Q y Patrick Artner, aquí hay una solución aún más generalizada.

# Assuming Python3
import sys

class ValidationError(ValueError):  # thanks Patrick Artner
    pass

def validate_input(prompt, cast=str, cond=(lambda x: True), onerror=None):
    if onerror==None: onerror = {}
    while True:
        try:
            data = cast(input(prompt))
            if not cond(data): raise ValidationError
            return data
        except tuple(onerror.keys()) as e:  # thanks Daniel Q
            print(onerror[type(e)], file=sys.stderr)

Opté por explícito ify raisedeclaraciones en lugar de un assert, porque la comprobación de afirmación puede estar desactivada, mientras que la validación siempre debe estar activada para proporcionar solidez.

Esto puede usarse para obtener diferentes tipos de entrada, con diferentes condiciones de validación. Por ejemplo:

# No validation, equivalent to simple input:
anystr = validate_input("Enter any string: ")

# Get a string containing only letters:
letters = validate_input("Enter letters: ",
    cond=str.isalpha,
    onerror={ValidationError: "Only letters, please!"})

# Get a float in [0, 100]:
percentage = validate_input("Percentage? ",
    cast=float, cond=lambda x: 0.0<=x<=100.0,
    onerror={ValidationError: "Must be between 0 and 100!",
             ValueError: "Not a number!"})

O, para responder la pregunta original:

age = validate_input("Please enter your age: ",
        cast=int, cond=lambda a:0<=a<150,
        onerror={ValidationError: "Enter a plausible age, please!",
                 ValueError: "Enter an integer, please!"})
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.")

1

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

0

Si bien a try/ exceptblock funcionará, sería una forma mucho más rápida y limpia de lograr esta tarea str.isdigit().

while True:
    age = input("Please enter your age: ")
    if age.isdigit():
        age = int(age)
        break
    else:
        print("Invalid number '{age}'. Try again.".format(age=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.")

0

¡Buena pregunta! Puede probar el siguiente código para esto. =)

Este código usa ast.literal_eval () para encontrar el tipo de datos de la entrada ( age). Luego sigue el siguiente algoritmo:

  1. Solicite al usuario que ingrese su / su age.

    1.1. Si agees floato inttipo de datos:

    • Comprueba si age>=18. Si age>=18, imprima la salida adecuada y salga.

    • Comprueba si 0<age<18. Si 0<age<18, imprima la salida adecuada y salga.

    • Si age<=0, solicite al usuario que ingrese nuevamente un número válido para la edad ( es decir, vuelva al paso 1.)

    1.2. Si ageno es floato inttipo de datos, solicite al usuario que ingrese su edad nuevamente ( es decir, regrese al paso 1).

Aquí está el código.

from ast import literal_eval

''' This function is used to identify the data type of input data.'''
def input_type(input_data):
    try:
        return type(literal_eval(input_data))
    except (ValueError, SyntaxError):
        return str

flag = True

while(flag):
    age = raw_input("Please enter your age: ")

    if input_type(age)==float or input_type(age)==int:
        if eval(age)>=18: 
            print("You are able to vote in the United States!") 
            flag = False 
        elif eval(age)>0 and eval(age)<18: 
            print("You are not able to vote in the United States.") 
            flag = False
        else: print("Please enter a valid number as your age.")

    else: print("Sorry, I didn't understand that.") 

0

Siempre puede aplicar una lógica simple if-else y agregar una iflógica más a su código junto con un forbucle.

while True:
     age = int(input("Please enter your age: "))
     if (age >= 18)  : 
         print("You are able to vote in the United States!")
     if (age < 18) & (age > 0):
         print("You are not able to vote in the United States.")
     else:
         print("Wrong characters, the input must be numeric")
         continue

Este será un baño infinito y se le pedirá que ingrese la edad, indefinidamente.


Esto realmente no responde la pregunta. La pregunta era acerca de obtener una entrada del usuario hasta que den una respuesta válida, no indefinidamente .
Georgy

-1

Puede escribir una lógica más general para permitir que el usuario ingrese solo un número específico de veces, ya que el mismo caso de uso surge en muchas aplicaciones del mundo real.

def getValidInt(iMaxAttemps = None):
  iCount = 0
  while True:
    # exit when maximum attempt limit has expired
    if iCount != None and iCount > iMaxAttemps:
       return 0     # return as default value

    i = raw_input("Enter no")
    try:
       i = int(i)
    except ValueError as e:
       print "Enter valid int value"
    else:
       break

    return i

age = getValidInt()
# do whatever you want to do.

1
olvidas aumentar el valor de iCount después de cada ciclo
Hoai-Thu Vuong

-1

Puede hacer que la declaración de entrada sea un ciclo True verdadero para que solicite repetidamente la entrada de los usuarios y luego rompa ese ciclo si el usuario ingresa la respuesta que desea. Y puede usar los bloques try y except para manejar respuestas no válidas.

while True:

    var = True

    try:
        age = int(input("Please enter your age: "))

    except ValueError:
        print("Invalid input.")
        var = False

    if var == True:
        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.")

La variable var es solo para que si el usuario ingresa una cadena en lugar de un entero, el programa no devolverá "No puede votar en los Estados Unidos".


-1

Use la instrucción "while" hasta que el usuario ingrese un valor verdadero y si el valor de entrada no es un número o es un valor nulo, omítalo e intente preguntar nuevamente y así sucesivamente. Por ejemplo, traté de responder verdaderamente tu pregunta. Si suponemos que nuestra edad está entre 1 y 150, entonces se acepta el valor de entrada, de lo contrario, es un valor incorrecto. Para finalizar el programa, el usuario puede usar la tecla 0 e ingresarla como valor.

Nota: Lea los comentarios al principio del código.

# If your input value is only a number then use "Value.isdigit() == False".
# If you need an input that is a text, you should remove "Value.isdigit() == False".
def Input(Message):
    Value = None
    while Value == None or Value.isdigit() == False:
        try:        
            Value = str(input(Message)).strip()
        except InputError:
            Value = None
    return Value

# Example:
age = 0
# If we suppose that our age is between 1 and 150 then input value accepted,
# else it's a wrong value.
while age <=0 or age >150:
    age = int(Input("Please enter your age: "))
    # For terminating program, the user can use 0 key and enter it as an a value.
    if age == 0:
        print("Terminating ...")
        exit(0)

if age >= 18 and age <=150: 
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

-1

Una solución más para usar la validación de entrada usando una ValidationErrorvalidación de rango personalizada y (opcional) para entradas enteras:

class ValidationError(ValueError): 
    """Special validation error - its message is supposed to be printed"""
    pass

def RangeValidator(text,num,r):
    """Generic validator - raises 'text' as ValidationError if 'num' not in range 'r'."""
    if num in r:
        return num
    raise ValidationError(text)

def ValidCol(c): 
    """Specialized column validator providing text and range."""
    return RangeValidator("Columns must be in the range of 0 to 3 (inclusive)", 
                          c, range(4))

def ValidRow(r): 
    """Specialized row validator providing text and range."""
    return RangeValidator("Rows must be in the range of 5 to 15(exclusive)",
                          r, range(5,15))

Uso:

def GetInt(text, validator=None):
    """Aks user for integer input until a valid integer is given. If provided, 
    a 'validator' function takes the integer and either raises a 
    ValidationError to be printed or returns the valid number. 
    Non integers display a simple error message."""
    print()
    while True:
        n = input(text)
        try:
            n = int(n)

            return n if validator is None else validator(n)

        except ValueError as ve:
            # prints ValidationErrors directly - else generic message:
            if isinstance(ve, ValidationError):
                print(ve)
            else:
                print("Invalid input: ", n)


column = GetInt("Pleased enter column: ", ValidCol)
row = GetInt("Pleased enter row: ", ValidRow)
print( row, column)

Salida:

Pleased enter column: 22
Columns must be in the range of 0 to 3 (inclusive)
Pleased enter column: -2
Columns must be in the range of 0 to 3 (inclusive)
Pleased enter column: 2
Pleased enter row: a
Invalid input:  a
Pleased enter row: 72
Rows must be in the range of 5 to 15(exclusive)
Pleased enter row: 9  

9, 2

-1

Aquí hay una solución más limpia y generalizada que evita los bloques repetitivos if / else: escriba una función que tome pares (Error, solicitud de error) en un diccionario y realice todas sus comprobaciones de valores con aserciones.

def validate_input(prompt, error_map):
    while True:
        try:
            data = int(input(prompt))
            # Insert your non-exception-throwing conditionals here
            assert data > 0
            return data
        # Print whatever text you want the user to see
        # depending on how they messed up
        except tuple(error_map.keys()) as e:
            print(error_map[type(e)])

Uso:

d = {ValueError: 'Integers only', AssertionError: 'Positive numbers only', 
     KeyboardInterrupt: 'You can never leave'}
user_input = validate_input("Positive number: ", d)

-1

Entrada persistente del usuario utilizando la función recursiva :

Cuerda

def askName():
    return input("Write your name: ").strip() or askName()

name = askName()

Entero

def askAge():
    try: return int(input("Enter your age: "))
    except ValueError: return askAge()

age = askAge()

y finalmente, el requisito de la pregunta:

def askAge():
    try: return int(input("Enter your age: "))
    except ValueError: return askAge()

age = askAge()

responseAge = [
    "You are able to vote in the United States!",
    "You are not able to vote in the United States.",
][int(age < 18)]

print(responseAge)

-2

La solución simple sería:

while True:
    age = int(input("Please enter your age: "))

    if (age<=0) or (age>120):
        print('Sorry, I did not understand that.Please try again')
        continue
    else:

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

Explicación del código anterior: para una edad válida, debe ser positiva y no debe ser superior a la edad física normal, por ejemplo, la edad máxima es 120.

Luego, podemos pedirle al usuario la edad y si la entrada de edad es negativa o más de 120, consideramos que es una entrada no válida y le pedimos al usuario que intente nuevamente.

Una vez que se ingresa la entrada válida, realizamos una verificación (usando una declaración anidada if-else) si la edad es> = 18 o viceversa e imprimimos un mensaje si el usuario es elegible para votar


"Ingrese su edad: dickety six": el mismo bloqueo que se indica en la pregunta ...
BDL

-2

tome la entrada como cadena y use isdigit () para verificar que la entrada solo tenga dígitos, no esté vacía, no puede ser -ve

while(True):
   #take input as string
   name = input('Enter age : ')
   #check if valid age, only digits
   print( name.isdigit() ) 

run output : 
Enter age : 12
True
Enter age : 
False
Enter age : qwd
False
Enter age : dw3
False
Enter age : 21de
False
Enter age : 1
True
Enter age : -1
False


Tampoco responde la pregunta.
Georgy
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.