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