[Tutor] How to handle a non integer within a question function that needs to return a integer?

Steven D'Aprano steve at pearwood.info
Thu Mar 12 03:16:07 CET 2015


On Wed, Mar 11, 2015 at 09:26:38PM +0000, Wibble wrote:
[...]
> How do I make it show a message to users saying only numbers excepted 
> and then the loop continues?
> 
> I have tried this but same error is still generated.
> 
> def user_choice(question, low, high, step = 1):
>     """Define user choice"""
>     choice = None
>     while choice not in range(low, high, step):
>         choice = int(input(question))

Your error occurs at the line above.

>         if choice == str(input(question)):
>             print('Numbers only please!')

And here you try to detect the error, but it's too late, since it has 
already happened!

>     return choice
> 
> Do I need to create another while loop within this loop to handle a 
> string input?

Yes you do, because the user might be stupid, clumsy, obnoxious, or a 
cat walking on the keyboard, and might repeatedly type non-numbers. Or 
they might innocently try typing number words like "one", "two", etc.

So let's break the question down. You want to give the user the option 
to enter a number, then you want to check that it is within a range.

Let's start by accepting any old number:


# Python 3 version!
# for Python 2, make sure you use raw_input() not input()
def enter_number(prompt):
    if not prompt.endswith(" "):
        # Make sure there is at least one space between the prompt
        # and where the user types.
        prompt += " "
    answer = input(prompt)
    while not answer.isdigit():
        print("You must enter a number!")
        answer = input(prompt)
    return int(answer)


If you try that, you will find a couple of limitations:

- it doesn't accept negative numbers;
- it doesn't accept floating point numbers like 1.2 or 3e5.


Here's another way to write the same function. It's a bit more verbose, 
but probably a better design:


def enter_number(prompt):
    """Ask the user to enter an integer number.

    Takes a single argument, the string to use to prompt the user.
    This repeats until they enter an actual number.
    """
    if not prompt.endswith(" "):
        # Make sure there is at least one space between the prompt
        # and where the user types.
        prompt += " "
    while True:  # Loop forever.
        answer = input(prompt)  # use raw_input in Python 2
        try:
            number = int(answer)
        except ValueError:
            print("You must enter a number!")
        else:
            return number


This loops forever, asking the user for a number, then trying to convert 
their answer into a number inside a `try` block. If the conversion 
fails, the `except` block runs, and the loop continues. But if the 
conversion succeeds, the `else` block runs, which returns the number, 
thus breaking the infinite loop.

Now you can use that as a building block to write the function you want:

def user_choice(question, low, high, step = 1):
    """A better docstring would be appropriate."""
    choice = None
    while choice not in range(low, high, step):
         choice = enter_number(question)
    return choice


There's a problem with this function too -- if the user enters a number 
out of range, they don't get any feedback as to why their answer is 
rejected, instead they just get asked for a number again. Very 
disheartening!

How about this?

def user_choice(question, num, high=None):
    """A better docstring would be appropriate."""
    if high is None:
        start = 1
        end = num
    else:
        start = num
        end = high
    allowed = range(start, end+1)
    while True:
        choice = enter_number(question)
        if choice in allowed:
            return choice
        else:
            print("Out of range! Try again.")



-- 
Steve


More information about the Tutor mailing list