CIS 2020 · Section 3.9 · Worked examples

More input validation examples

The Module 6 review covers the basic validation loop: keep asking while the input is bad. These six examples cover the shapes that pattern actually takes in practice, from a simple range check to catching the wrong type entirely to combining two rules in one loop.

Example 1

Range check: a number between 1 and 100

The loop condition is the opposite of the rule you want, and it names both ends of the range with or.

score = int(input("Enter a score from 1 to 100: "))

while score < 1 or score > 100 :
    print("That's outside the allowed range.")
    score = int(input("Enter a score from 1 to 100: "))

print("Accepted:", score)

The rule you actually want is 1 <= score <= 100. The loop condition is its negation: keep looping while the score is below 1 or above 100. Writing the loop condition as the direct opposite of the acceptance rule, rather than trying to invent a new condition from scratch, is the fastest way to get validation loops right.

Example 2

Non-empty string: a required name field

Not every rule is numeric. A string can be invalid just by being empty, or by being nothing but spaces.

name = input("Enter your name: ").strip()

while name == "" :
    print("Name cannot be blank.")
    name = input("Enter your name: ").strip()

print("Hello,", name)

.strip() removes leading and trailing whitespace before the check runs, so a user who types only spacebar presses is caught by the same == "" test as a user who presses enter with nothing typed at all. Without .strip(), " " would pass the check even though it is not a usable name.

Example 3

Catching the wrong type entirely

A range check assumes the conversion to int already succeeded. When the user might type letters instead of digits, the conversion itself has to be inside the loop, guarded by try and except.

while True :
    userInput = input("Enter your age as a whole number: ")
    try :
        age = int(userInput)
        break
    except ValueError :
        print("That's not a whole number. Try again.")

print("Age accepted:", age)

This uses while True, an intentionally infinite loop, because the exit condition is not a simple comparison, it is "the conversion worked." break is what actually leaves the loop, and it only runs on the line right after a successful int(userInput). If the conversion raises ValueError, the except block runs instead, break is skipped, and the loop goes around again.

Why not while age is not a number:? That is not valid Python; there is no way to test "would this string convert to an int" without actually trying the conversion. try/except exists precisely for rules that can only be checked by attempting the risky operation and seeing whether it fails.

Example 4

Restricting input to a fixed menu of choices

Sometimes "valid" means "one of a short, specific list," not a range or a type at all.

choice = input("Choose a size (small, medium, large): ").lower()

while choice != "small" and choice != "medium" and choice != "large" :
    print("Please type small, medium, or large.")
    choice = input("Choose a size (small, medium, large): ").lower()

print("You chose:", choice)

.lower() is doing real work here: it means a user who types "Medium" or "MEDIUM" is still accepted, because the comparison always runs against a lowercase version of whatever was typed. The same idea can be written more compactly with the in operator once you've seen it: while choice not in ("small", "medium", "large") : means exactly the same thing.

Example 5

Two rules at once: a simple password check

Real validation rules are rarely just one condition. This one needs both a length rule and a content rule to pass at the same time, straight out of Section 3.7's and.

password = input("Create a password (8+ characters, at least one digit): ")

hasDigit = False
for character in password :
    if character.isdigit() :
        hasDigit = True

while len(password) < 8 or not hasDigit :
    print("Password must be 8+ characters and include at least one digit.")
    password = input("Create a password (8+ characters, at least one digit): ")
    hasDigit = False
    for character in password :
        if character.isdigit() :
            hasDigit = True

print("Password accepted.")

The password is only accepted once both conditions are satisfied together: long enough and containing a digit. The loop condition, as always, is the opposite: keep looping while it's too short or missing a digit. Notice that flipping and to or when you negate a compound condition is not optional, it is exactly what the rule requires; this is the same pattern Section 3.7 covers as De Morgan's laws.

Example 6

Putting it together: type and range in the same loop

The most realistic validation combines everything above: the input might be the wrong type, or the right type but the wrong value, and both have to be handled in the same loop.

while True :
    userInput = input("Enter a temperature between -50 and 150: ")
    try :
        temperature = float(userInput)
    except ValueError :
        print("That's not a number. Try again.")
        continue

    if temperature < -50 or temperature > 150 :
        print("That's outside a realistic range. Try again.")
        continue

    break

print("Temperature accepted:", temperature)

Here continue does the same job break did in Example 3, but in reverse: it skips the rest of the current pass and jumps straight back to the top of the loop, so a bad conversion never falls through into the range check below it. Only a value that survives both the try block and the range check ever reaches break.

Tracing three different inputs through this loop.
Input typedWhat happens
"abc"float("abc") raises ValueError; message printed, continue restarts the loop.
"200"Converts fine, but 200 > 150; message printed, continue restarts the loop.
"98.6"Converts fine and is in range; break runs, the loop ends.