Categories
input python validation

Asking the user for input until they give a valid response

707

I am writing a program that accepts user input.

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

The program works as expected as long as the the user enters meaningful data.

Please enter your age: 23
You are able to vote in the United States!

But it fails if the user enters invalid data:

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'

Instead of crashing, I would like the program to ask for the input again. Like this:

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!

How do I ask for valid input instead of crashing or accepting invalid values (e.g. -1)?

0

    923

    +750

    The simplest way to accomplish this is to put the input method in a while loop. Use continue when you get bad input, and break out of the loop when you’re satisfied.

    When Your Input Might Raise an Exception

    Use try and except to detect when the user enters data that can’t be parsed.

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

    Implementing Your Own Validation Rules

    If you want to reject values that Python can successfully parse, you can add your own validation logic.

    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
    

    Combining Exception Handling and Custom Validation

    Both of the above techniques can be combined into one loop.

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

    Encapsulating it All in a Function

    If you need to ask your user for a lot of different values, it might be useful to put this code in a function, so you don’t have to retype it every time.

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

    Putting It All Together

    You can extend this idea to make a very generic input function:

    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
    

    With usage such as:

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

    Common Pitfalls, and Why you Should Avoid Them

    The Redundant Use of Redundant input Statements

    This method works but is generally considered poor style:

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

    It might look attractive initially because it’s shorter than the while True method, but it violates the Don’t Repeat Yourself principle of software development. This increases the likelihood of bugs in your system. What if you want to backport to 2.7 by changing input to raw_input, but accidentally change only the first input above? It’s a SyntaxError just waiting to happen.

    Recursion Will Blow Your Stack

    If you’ve just learned about recursion, you might be tempted to use it in get_non_negative_int so you can dispose of the while loop.

    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
    

    This appears to work fine most of the time, but if the user enters invalid data enough times, the script will terminate with a RuntimeError: maximum recursion depth exceeded. You may think “no fool would make 1000 mistakes in a row”, but you’re underestimating the ingenuity of fools!

    8

    • 82

      Its fun reading it with many examples, kudos. Underrated lesson: “Don’t underestimate the ingenuity of fools!”

      – vpibano

      Jan 3, 2017 at 2:02

    • 5

      Not only would I have upvoted both the Q&A anyway, as they’re great, but you sealed the deal with “dickety six”. Well done, @Kevin.

      – erekalper

      Feb 2, 2018 at 15:58

    • 2

      Don’t estimate the ingenuity of fools… and clever attackers. A DOS attack would be easiest for this sort of thing, but others may be possible.

      Apr 28, 2019 at 2:53

    • 1

      @JArunMani I don’t think it would be poor style, but might be a little less readable. You will indeed have only one input per loop and the loop will become very short, but the condition might become pretty long…

      – Tomerikoo

      May 9, 2020 at 8:40

    • 4

      @laundmo,certainly I release the code blocks that I wrote into the public domain. Feel free to use them in any context, without my explicit permission or knowledge. Regarding the non-code-block segments, If you want to paste my entire answer into a “Learn Python” book you’re writing, let’s talk royalties 😉

      – Kevin

      Jul 24, 2020 at 13:50


    54

    Why would you do a while True and then break out of this loop while you can also just put your requirements in the while statement since all you want is to stop once you have the age?

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

    This would result in the following:

    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.
    

    this will work since age will never have a value that will not make sense and the code follows the logic of your “business process”

    1

    • A well-designed exit-condition 🤩️ like advised here avoids the pitfall of infinite loop caused by while True without safely reaching break or return.

      – hc_dev

      Jun 17 at 17:48


    27

    Though the accepted answer is amazing. I would also like to share a quick hack for this problem. (This takes care of the negative age problem as well.)

    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. This code is for python 3.x.

    6

    • 1

      Note that this code is recursive, but recursion isn’t necessary here, and as Kevin said, it can blow your stack.

      – PM 2Ring

      Jan 31, 2016 at 8:12

    • 3

      @PM2Ring – you are right. But my purpose here was just to show how “short circuiting” can minimise (beautify) long pieces of code.

      – aaveg

      Feb 3, 2016 at 8:58

    • 15

      Why would you assign a lambda to a variable, just use def instead. def f(age): is far clearer than f = lambda age:

      – GP89

      May 16, 2017 at 22:29


    • 4

      In some cases, you may need the age just once and then there is no use of that function. One may want to use a function and throw it away after the job is done. Also, this may not be the best way, but it definitely is a different way of doing it (which was the purpose of my solution).

      – aaveg

      May 16, 2017 at 23:17


    • @aaveg how would you turn this code to actually save the age provided by the user?

      Jul 4, 2019 at 20:04