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 se si desidera avere un messaggio "input errato" separato da un prompt di input come in altre risposte:
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
Come funziona?
prompts = chain(["Enter a number: "], repeat("Not a number! Try again: "))
Questa combinazione di itertools.chaine itertools.repeatcreerà un iteratore che produrrà stringhe "Enter a number: "una volta e "Not a number! Try again: "un numero infinito di volte:
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)- qui mapverranno applicate tutte le promptsstringhe del passaggio precedente alla inputfunzione. Per esempio:
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...
- Usiamo
filtere str.isdigitfiltriamo quelle stringhe che contengono solo cifre:
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...
E per ottenere solo la prima stringa di sole cifre che usiamo next.
Altre regole di convalida:
Metodi di stringa: puoi ovviamente usare altri metodi di stringa come str.isalphaottenere solo stringhe alfabetiche o solo lettere str.isuppermaiuscole. Consulta i documenti per l'elenco completo.
Test di appartenenza:
esistono diversi modi per eseguirlo. Uno di questi è utilizzando il __contains__metodo:
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
Confronto di numeri:
ci sono metodi di confronto utili che possiamo usare qui. Ad esempio, per __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
Oppure, se non ti piace usare i metodi dunder (dunder = double-underscore), puoi sempre definire la tua funzione o usare quelli del operatormodulo.
Esistenza del percorso:
qui è possibile utilizzare la pathliblibreria e il suo Path.existsmetodo:
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
Numero limite di tentativi:
Se non vuoi torturare un utente chiedendogli qualcosa un numero infinito di volte, puoi specificare un limite in una chiamata di itertools.repeat. Questo può essere combinato con la fornitura di un valore predefinito alla nextfunzione:
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!
Preelaborazione dei dati di input:
A volte non vogliamo rifiutare un input se l'utente lo fornisce accidentalmente IN MAIUSCOLO o con uno spazio all'inizio o alla fine della stringa. Per tenere conto di questi semplici errori, possiamo preelaborare i dati di input applicando str.lowere str.stripmetodi. Ad esempio, nel caso di test di appartenenza il codice sarà simile al seguente:
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
Nel caso in cui si disponga di molte funzioni da utilizzare per la preelaborazione, potrebbe essere più semplice utilizzare una funzione che esegue una composizione di funzioni . Ad esempio, usando quello da qui :
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
Combinazione di regole di convalida:
Per un semplice caso, ad esempio, quando il programma richiede un'età compresa tra 1 e 120 anni, si può semplicemente aggiungere un altro 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)
Ma nel caso in cui ci siano molte regole, è meglio implementare una funzione che esegue una congiunzione logica . Nel seguente esempio ne userò uno pronto da qui :
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
Sfortunatamente, se qualcuno ha bisogno di un messaggio personalizzato per ogni caso fallito, quindi, temo, non esiste un modo abbastanza funzionale. O almeno non riuscivo a trovarne uno.