This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Validate Your Input

Check every value that enters your program before you use it.

    Why this matters

    Everything that comes into your program from outside is untrusted until you check it. input() always returns a string, and the user can type anything: letters where you expected a number, a negative quantity, a blank line, or ten thousand characters. If your program reads files, those files may be edited to contain something malicious or unexpected.

    If you use a bad input value without checking it, one of three things happens. The program crashes with a traceback. The program keeps running with bad data, and the problem shows up later somewhere confusing. Or, if the value reaches something powerful like a file path or a database query, the user can make your program do things you never intended. Validation stops all three.

    The rule: Every value that enters the program is checked for type, range, and format before it is used. Values that fail the check are rejected with a clear message. The program never “repairs” bad input by guessing.

    Benefits

    • Errors are caught at the door, where you know exactly what went wrong and can tell the user.
    • The rest of the code can assume its inputs are sane, which makes it simpler.
    • Bad data never reaches your files or your calculations.
    • Deliberate misuse (a path like ../../secrets.txt, a quantity of -1000) is stopped at the same place as honest mistakes.

    Red flags (violations)

    • int(input(...)) or float(input(...)) with no try/except around it.
    • A menu that reads a choice and uses it without checking that it is one of the options.
    • A quantity, price, or age that is converted to a number but never checked for a sensible range.
    • A filename from the user passed straight to open().
    • Code that “fixes” bad input silently, such as abs(quantity) to make a negative number positive.

    Example 1 - A number that is not a number

    Problematic code

    def ask_quantity() -> int:
        return int(input("Quantity: "))
    

    Type five and the program dies:

    ValueError: invalid literal for int() with base 10: 'five'
    

    Type -3 and the program accepts it, and now the inventory has a negative count.

    Fixed with type and range checks

    def ask_quantity() -> int:
        """Ask until the user enters a whole number greater than zero."""
        while True:
            raw = input("Quantity: ").strip()
            try:
                quantity = int(raw)
            except ValueError:
                print(f"'{raw}' is not a whole number. Try again.")
                continue
            if quantity <= 0:
                print("Quantity must be greater than zero. Try again.")
                continue
            return quantity
    

    Why this is better: The conversion is wrapped in try/except ValueError, so a bad string produces a message instead of a crash. The range check rejects zero and negatives. The loop keeps asking until the input is valid, so every caller of ask_quantity() can trust the number it gets back.

    Example 2 - A menu choice

    Problematic code

    def main_menu():
        print("1. Add item  2. Remove item  3. List items  4. Quit")
        choice = input("Choose: ")
        if choice == "1":
            add_item()
        elif choice == "2":
            remove_item()
        elif choice == "3":
            list_items()
        elif choice == "4":
            save_and_quit()
    

    Type 7 and nothing happens. Type 1 with a trailing space and nothing happens. The user has no idea why.

    Fixed by checking against the valid set

    MENU_CHOICES = {"1", "2", "3", "4"}
    
    def ask_menu_choice() -> str:
        """Ask until the user enters one of the menu numbers."""
        while True:
            choice = input("Choose: ").strip()
            if choice in MENU_CHOICES:
                return choice
            print(f"'{choice}' is not an option. Enter 1, 2, 3, or 4.")
    
    def main_menu():
        print("1. Add item  2. Remove item  3. List items  4. Quit")
        choice = ask_menu_choice()
        if choice == "1":
            add_item()
        elif choice == "2":
            remove_item()
        elif choice == "3":
            list_items()
        else:
            save_and_quit()
    

    Why this is better: The valid choices are written down once. The input is stripped of stray whitespace and checked against that set before the if chain runs. The if chain can now use a plain else for the last option because every other value has already been rejected.

    Example 3 - A filename from the user

    Problematic code

    def load_inventory():
        filename = input("Load which file? ")
        with open(filename) as f:
            return f.read()
    

    The user can type ../../.ssh/id_rsa or /etc/passwd and your program will read it for them.

    Fixed by restricting what is allowed

    from pathlib import Path
    
    DATA_DIR = Path("data")
    
    def ask_data_filename() -> Path:
        """Ask for a filename inside the data folder. Reject paths that leave it."""
        while True:
            name = input("Load which file? ").strip()
            if not name or "/" in name or "\\" in name or name.startswith("."):
                print("Enter a plain filename with no folders, for example inventory.txt.")
                continue
            path = DATA_DIR / name
            if not path.is_file():
                print(f"There is no file named {name} in the data folder.")
                continue
            return path
    

    Why this is better: The program decides where files live. The user only supplies a name, and anything that looks like a path is rejected. The check that the file exists happens here, where the program can ask again, instead of deep inside the loading code.

    How to apply this rule

    1. Find every entry point. Search your project for input(, open(, sys.argv, and any place a file is read. Each one is a place where untrusted data enters.
    2. Decide what valid means. For each entry point, write down the type, the allowed range or set of values, and the format.
    3. Check before you use. Convert and check the value in one small function, like ask_quantity(). Return only valid values.
    4. Reject, do not repair. When a value fails, tell the user what was wrong and ask again. Do not guess what they meant.
    5. Keep the check near the input. The function that reads the value is the right place to validate it. Code further in should be able to trust what it receives.

    Knowledge Check

    • A program reads a price with price = float(input("Price: ")). What happens when the user types $5?
      1. Python strips the dollar sign and stores 5.0
      2. The program raises ValueError and crashes unless the call is inside a try block
      3. price is set to None
      4. The program asks again automatically
      Answer2 — `float("$5")` raises `ValueError`. Without a `try`/`except`, the exception propagates up and ends the program with a traceback.
    • Which check is missing from this code? age = int(input("Age: ")) inside a try/except ValueError that prints a message and asks again.
      1. A type check
      2. A range check
      3. A format check for letters
      4. Nothing is missing
      Answer2 — the `try`/`except` handles the type, but `-40` and `9999` are both accepted as valid ages. A range check is still needed.
    • Why is it a bad idea to “fix” a negative quantity with quantity = abs(quantity)?
      1. abs() is slow
      2. The user typed something wrong, and the program is guessing what they meant instead of asking
      3. Negative numbers cannot be stored in Python
      4. It is fine; this is the recommended approach
      Answer2 — silently changing bad input hides the mistake from the user. If they meant to type 10 and typed -1, the program now records 1 and nobody knows.
    • A user is asked for a filename and types ../config/passwords.txt. What is the safest response?
      1. Open it, since the user asked for it
      2. Strip the ../ and open config/passwords.txt
      3. Reject any name containing a path separator and ask again
      4. Open it only if it exists
      Answer3 — the program should control which folder files come from. Any input that tries to leave that folder is rejected outright. Stripping the `../` still lets the user reach a folder you did not intend.

    Next up

    Validation stops bad values at the door. The next principle is about what to do when something still goes wrong: handle exceptions at the lowest sensible level.