760 votes

Demander à l'utilisateur de saisir des données jusqu'à ce qu'il donne une réponse valide.

J'écris un programme qui doit accepter les entrées de l'utilisateur.

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

Cela fonctionne comme prévu si l'utilisateur saisit des données sensées.

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

Mais s'ils font une erreur, alors il se plante :

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'

Au lieu de se planter, je voudrais qu'il essaie à nouveau de recevoir l'entrée. Comme ceci :

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!

Comment puis-je y parvenir ? Et si je voulais également rejeter des valeurs comme -1 ce qui est un valide int mais absurde dans ce contexte ?

16 votes

Je pense qu'il y a un bug dans l'un de vos commentaires, il devrait se lire comme suit #note: Python 2.7 users should damn-well get their act together and update :-)

998voto

Kevin Points 17955

La manière la plus simple d'y parvenir serait de mettre le fichier input dans une boucle while. Utilisez continue quand vous avez de mauvaises données, et break sortir de la boucle quand vous êtes satisfait.

Quand votre entrée peut soulever une exception

Utilisez essayer et attraper pour détecter quand l'utilisateur entre des données qui ne peuvent pas être analysées.

while True:
    try:
        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.")

Mise en œuvre de vos propres règles de validation

Si vous souhaitez rejeter des valeurs que Python peut analyser avec succès, vous pouvez ajouter votre propre logique de validation.

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

Combinaison de la gestion des exceptions et de la validation personnalisée

Les deux techniques ci-dessus peuvent être combinées en une seule boucle.

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

Tout encapsuler dans une fonction

Si vous devez demander à votre utilisateur un grand nombre de valeurs différentes, il peut être utile de placer ce code dans une fonction, afin de ne pas avoir à le retaper à chaque fois.

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

Tout mettre en place

Vous pouvez étendre cette idée pour créer une fonction d'entrée très générique :

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 

Avec des usages tels que :

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

Les pièges les plus courants, et pourquoi vous devriez les éviter

L'utilisation redondante de la redondance input Déclarations

Cette méthode fonctionne mais est généralement considérée comme un style médiocre :

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

Elle peut sembler attrayante au premier abord parce qu'elle est plus courte que la while True mais cela enfreint la Ne vous répétez pas principe du développement de logiciels. Cela augmente la probabilité d'apparition de bogues dans votre système. Et si vous voulez faire un backport vers la 2.7 en changeant input à raw_input mais ne modifie accidentellement que le premier input ci-dessus ? C'est un SyntaxError qui attendent de se produire.

La récursion va faire exploser votre pile

Si vous venez d'apprendre ce qu'est la récursion, vous pourriez être tenté de l'utiliser en get_non_negative_int afin que vous puissiez disposer de la boucle 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

Cela semble fonctionner correctement la plupart du temps, mais si l'utilisateur entre des données invalides suffisamment de fois, le script se terminera avec un RuntimeError: maximum recursion depth exceeded . Vous pensez peut-être que "aucun imbécile ne ferait 1000 erreurs d'affilée", mais vous sous-estimez l'ingéniosité des imbéciles !

68 votes

C'est amusant de le lire avec de nombreux exemples, bravo. Leçon sous-estimée : "Ne sous-estimez pas l'ingéniosité des imbéciles !"

3 votes

Non seulement j'aurais upvoted les deux Q & R de toute façon, comme ils sont grands, mais vous avez scellé l'affaire avec "dickety six". Bien joué, @Kevin.

2 votes

N'estimez pas l'ingéniosité des imbéciles... et des attaquants malins. Une attaque DOS serait la plus simple pour ce genre de chose, mais d'autres sont possibles.

65voto

Steven Stip Points 1

Pourquoi ferais-tu un while True et ensuite sortir de cette boucle alors que vous pouvez aussi simplement mettre vos exigences dans l'instruction while puisque tout ce que vous voulez est de vous arrêter une fois que vous avez l'âge ?

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

Il en résulterait ce qui suit :

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.

cela fonctionnera puisque age n'aura jamais une valeur qui n'aura pas de sens et que le code suit la logique de votre "processus commercial".

43voto

Georgy Points 2053

Approche fonctionnelle ou " Regarde maman, pas de boucles ! " :

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

ou si vous voulez avoir un message "mauvaise saisie" séparé d'une invite de saisie comme dans les autres réponses :

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

Comment cela fonctionne-t-il ?

  1. prompts = chain(["Enter a number: "], repeat("Not a number! Try again: "))

    Cette combinaison de itertools.chain y itertools.repeat créera un itérateur qui donnera des chaînes de caractères "Enter a number: " une fois, et "Not a number! Try again: " un nombre infini de fois :

    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) - ici map appliquera tous les prompts de l'étape précédente au input fonction. Par exemple :

    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. Nous utilisons filter y str.isdigit pour filtrer les chaînes de caractères qui ne contiennent que des chiffres :

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

    Et pour obtenir uniquement la chaîne des premiers chiffres, nous utilisons next .

Autres règles de validation :

  1. Les méthodes de type "String" : Bien sûr, vous pouvez utiliser d'autres méthodes de chaîne comme str.isalpha pour obtenir uniquement des chaînes alphabétiques, ou str.isupper pour obtenir uniquement des majuscules. Voir docs pour la liste complète.

  2. Test d'adhésion :
    Il existe plusieurs manières différentes de l'exécuter. L'une d'entre elles consiste à utiliser __contains__ méthode :

    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. Comparaison des chiffres :
    Il existe des méthodes de comparaison utiles que nous pouvons utiliser ici. Par exemple, pour __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

    Ou, si vous n'aimez pas utiliser les méthodes dunder (dunder = double-underscore), vous pouvez toujours définir votre propre fonction, ou utiliser celles de la base de données de l'UE. operator module.

  4. Existence d'un chemin :
    Ici, on peut utiliser pathlib et sa bibliothèque Path.exists méthode :

    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

Limitation du nombre d'essais :

Si vous ne voulez pas torturer un utilisateur en lui demandant quelque chose un nombre infini de fois, vous pouvez spécifier une limite dans un appel à la fonction itertools.repeat . Cela peut être combiné avec la fourniture d'une valeur par défaut à l'option next fonction :

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!

Prétraitement des données d'entrée :

Parfois, nous ne voulons pas rejeter une entrée si l'utilisateur l'a accidentellement fournie. EN MAJUSCULES ou avec un espace au début ou à la fin de la chaîne. Pour tenir compte de ces simples erreurs, nous pouvons prétraiter les données d'entrée en appliquant la méthode suivante str.lower y str.strip méthodes. Par exemple, pour le cas du test d'adhésion, le code ressemblera à ceci :

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

Dans le cas où vous avez de nombreuses fonctions à utiliser pour le prétraitement, il peut être plus facile d'utiliser une fonction effectuant un composition des fonctions . Par exemple, en utilisant celui de ici :

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

Combinaison de règles de validation :

Pour un cas simple, par exemple, lorsque le programme demande un âge compris entre 1 et 120, on peut simplement ajouter un autre fichier 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)

Mais dans le cas où il y a beaucoup de règles, il est préférable d'implémenter une fonction réalisant un conjonction logique . Dans l'exemple suivant, j'utiliserai une version toute prête de ici :

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

Malheureusement, si quelqu'un a besoin d'un message personnalisé pour chaque cas d'échec, j'ai bien peur qu'il n'y ait pas d'autre solution. joli de manière fonctionnelle. Ou, du moins, je n'en ai pas trouvé.

1 votes

Quelle réponse approfondie et merveilleuse, la décomposition des explications était formidable.

0 votes

En utilisant votre style, comment faire pour supprimer les espaces et mettre en minuscules les entrées pour les tests d'adhésion ? Je ne veux pas créer un ensemble qui doit inclure des exemples en majuscules et en minuscules. Je voudrais également autoriser les erreurs de saisie des espaces blancs.

1 votes

@Austin J'ai ajouté une nouvelle section sur le prétraitement. Jetez-y un coup d'oeil.

30voto

aaveg Points 1654

Bien que la réponse acceptée soit étonnante. J'aimerais également partager un hack rapide pour ce problème. (Cela prend en charge le problème de l'âge négatif également).

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

P.S. Ce code est pour python 3.x.

1 votes

Notez que ce code est récursif, mais la récursion n'est pas nécessaire ici, et comme Kevin l'a dit, elle peut faire sauter votre pile.

3 votes

@PM2Ring - vous avez raison. Mais mon but ici était juste de montrer comment le "court-circuitage" peut minimiser (embellir) de longs morceaux de code.

15 votes

Pourquoi assigner un lambda à une variable, utilisez simplement def à la place. def f(age): est bien plus clair que f = lambda age:

24voto

Georgy Points 2053

Utilisation de Cliquez sur :

Cliquez sur est une bibliothèque pour les interfaces de ligne de commande et elle fournit une fonctionnalité pour demander une réponse valide à un utilisateur.

Un exemple 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

Notez comment il a converti automatiquement la valeur de la chaîne de caractères en une valeur flottante.

Vérifier si une valeur est comprise dans un intervalle :

Il existe différents types personnalisés fourni. Pour obtenir un nombre dans une fourchette spécifique, nous pouvons utiliser 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

On peut aussi spécifier une seule des limites, min o 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

Test d'adhésion :

Utilisation de click.Choice type. Par défaut, cette vérification est sensible à la casse.

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

Travailler avec des chemins et des fichiers :

Utilisation d'un click.Path nous pouvons vérifier les chemins existants et les résoudre :

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 lecture et l'écriture de fichiers peuvent être effectuées par 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!

Autres exemples :

Confirmation du mot de passe :

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

Valeurs par défaut :

Dans ce cas, il suffit d'appuyer sur Enter (ou toute autre touche que vous utilisez) sans saisir de valeur, vous obtiendrez une valeur par défaut :

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

0 votes

Merci, c'est parfait. Boucler avec une sélection de nombres dans une plage valide était exactement ce que je recherchais.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X