In many cases we will want to ask the user for input. Generally we want to make sure the user has entered something helpful. In this post we will describe one way to accomplish this. We will break this task down into three parts:
Let's begin by asking the user for any number between 1 and 10 inclusive.
some_number = float(input("Please enter a number between 1 and 10 inclusive >> "))
This is fine but it's generally bad practice to hard-code numbers into our code. We will alter the above to become
lower = 1
higher = 10
question_to_ask = "Please enter a number between {} and {} >> ".format(lower, higher)
some_number = float(input(question_to_ask))
In this we have used string formatting. Using the variables lower and higher we can now test the user's input
lower = 1
higher = 10
question_to_ask = "Please enter a number between {} and {} >> ".format(lower, higher)
some_number = float(input(question_to_ask))
if some_number < lower or some_number > higher:
print("This number isn't within the range!")
else:
print("This number is within the range!")
Great! This will correctly make sure the user enters a number within the range. Let's tidy this up into a function like so
def ask_for_a_number(lower, higher):
"""
Asks user for a number between lower and higher inclusive
"""
question_to_ask = "Please enter a number between {} and {} >> ".format(lower, higher)
some_number = float(input(question_to_ask))
if some_number < lower or some_number > higher:
print("This number isn't within the range!")
else:
print("This number is within the range!")
return some_number
answer = ask_for_a_number(1, 10)
print("The user entered {}".format(answer))
Lovely. Our code successfully asks for a number and tests whether it is within the specified range. Even better we've cleverly put the return inside the IF so only valid numbers will be returned. Not entering a valid number will cause
>>> Please enter a number between 1 and 10 >> 11 This number isn't within the range! The user entered None
None can be useful but for our purposes this isn't good enough. We want to ask the user again.
What we need is a loop. Let's say we're pretty vindictive and the user won't be allowed to continue unless they give us valid input. So we want an infinite loop. We then want to escape this loop if and only if the user gives us a valid answer. Let's add a loop to our above code
def ask_for_a_number(lower, higher):
"""
Asks user for a number between lower and higher inclusive
"""
while True:
question_to_ask = "Please enter a number between {} and {} >> ".format(lower, higher)
some_number = float(input(question_to_ask))
if some_number < lower or some_number > higher:
print("This number isn't within the range!")
else:
return some_number
answer = ask_for_a_number(1, 10)
print("The user entered {}".format(answer))
Great all done. When users enter numbers outside this range the next iteration of the loop will begin. In the above the only way to leave the loop is the return statement.
This is good enough for some purposes but not ours! We want to reject all invalid inputs. To do this we will need to cover a new concept.
We cannot force the user to enter just numbers (short of removing most of the keyboard keys). Therefore we need to catch when they accidentally or otherwise enter something silly. For instance using our current function if the user enters potato we will get
>>> Please enter a number between 1 and 10 >> potato
Hopefully the unability for float() to turn potato into a number doesn't surprise you. What we need is some way to catch this error and tell the computer its just the user being and idiot and ignore them. For that we need the TRY-EXCEPT. Consider the simple example below
try:
infinite_division = 10 / 0
except ZeroDivisionError:
print("That's crazy stop that!")
Here the code inside the try indent is run. If it fails with a ZeroDivisionError, it will run the except block. In this way the program won't crash and it will keep going. For our code we need to reject inputs that aren't numbers. As we showed above, this is a ValueError. So we will alter our code above to take this account
def ask_for_a_number(lower, higher):
"""
Asks user for a number between lower and higher inclusive
"""
while True:
question_to_ask = "Please enter a number between {} and {} >> ".format(lower, higher)
some_number = input(question_to_ask)
try:
some_number = float(some_number)
except ValueError:
print("{} is not a number!".format(some_number))
continue
if some_number < lower or some_number > higher:
print("This number isn't within the range!")
else:
return some_number
answer = ask_for_a_number(1, 10)
print("The user entered {}".format(answer))
Note that we've moved the float conversion and added the try except cases. The above is now essentially complete. We've rejected invalid inputs - both by tests and by type. Whilst not perfect (the above has at least one bug!)
Let's explore the Try-Except a bit more with a different input from the user.
The biggest advantage of using Try-Except is that we don't actually care how the error is thrown. We just care that we caught it. In this way we can use other people's code for our purposes if they have raised errors. For instance let's say we want to ask the user for a date but it must be a weekday. So for example we want both 8/11/2017 and 11/11/2017 to pass as dates. However we want 11/11/2017 to fail as it is a Saturday. Whilst we could do this from scratch, this would be far far too much work for such a simple task.
In many cases in Python there will be a library to help us. There are two options available.
datetime is part of the standard library and so we may prefer to use this as it will just work. In this case if we're sure of the format the user will enter the dates we may simply use something like
from datetime import datetime
new_date = '11/11/2017'
new_date = datetime.strptime('11/11/2017', '%d/%m/%Y')
print(new_date)
With dateutil the Parser will guess the format rather than use having to specify it. For example
from dateutil import parser
new_date = '11/11/2017'
new_date = parser.parse(new_date)
print(new_date)
Even though we will need to install dateutil, it is going to be much easier from a user perspective to use. Okay so how do we figure out what error we need to catch to prevent an invalid date being passed? Simplest way is simply to trigger the error!
from dateutil import parser
new_date = '29/02/2017' # Hint - it's not a leap year!
new_date = parser.parse(new_date)
print(new_date)
will give something like
>>> Traceback (most recent call last): File "/home/mark/tmp.py", line 5, in <module> new_date = parser.parse(new_date) File "/usr/lib/python3/dist-packages/dateutil/parser.py", line 1008, in parse return DEFAULTPARSER.parse(timestr, **kwargs) File "/usr/lib/python3/dist-packages/dateutil/parser.py", line 404, in parse ret = default.replace(repl**) ValueError: day is out of range for month
Great so we can alter our code to become
from dateutil import parser
new_date = '29/02/2017' # Hint - it's not a leap year!
try:
new_date = parser.parse(new_date)
print(new_date)
except ValueError as e:
print("Invalid date!:", e)
Note how we use the as e to catch the specific error thrown by the parser. This way we can tell the user precisely what they did wrong!
So let's ask the user for a date and get a try-except clause to trigger on a ValueError. Here's code that will perform this task
from dateutil import parser
def ask_for_a_weekday_date():
"""
Asks for a valid date that happens to be a weekday
"""
question_to_ask = "Please enter a date >> "
while True:
new_date = input(question_to_ask)
try:
new_date = parser.parse(new_date)
except ValueError as e:
print("Invalid date!:", e)
continue
if new_date.weekday() < 5: # 5 and 6 correspond to saturday and sunday
return new_date
else:
print("This isn't a weekday!")
Here we are using the weekday() function. This will return 0 to 6 depending on the day of the week, pretty neat!
Ensuring the user enters valid input is essential for keeping our code from crashing in various inconvenient ways. In this post we've covered a pattern to perform this task and explored this using numbers and dates.