Layer 3 Session 22 State & Interactivity

Handling User Input

State changes need a trigger. In a real program, that trigger is often a user typing something. We connect input() to the controlled methods from Session 21.

input() validate โœ“

A person types something โ€” and the program has to react safely.

Estimated time: 35โ€“40 minutes  ยท  Pre-quiz โ†’ Concept โ†’ Lab โ†’ Commit โ†’ Post-quiz

1. Learning Objective

By the end of this session you will be able to:

  • Read a line of text from the user with input()
  • Explain why input() always returns a string, and why that matters for numeric fields
  • Convert and validate user input before passing it into a controlled state method
  • Build a small text-based menu loop that dispatches to different actions
  • Handle invalid user input (non-numeric text) without crashing the program

2. Pre-Coding Quiz

Answer these before reading the concept explanation or writing any code. It is fine to get these wrong โ€” that is the point. You need 4/5 to proceed to the lab.

Question 1 of 5

What is the type of the value returned by input("Enter population: "), regardless of what the user types?

input() always returns a string, no matter what characters the user typed. If you need a number, you must convert it yourself, typically with int() or float(), and handle the case where the conversion fails.
Question 2 of 5

A user types "fifty" when asked for a population. What happens if you call int(user_input) directly with no error handling?

int() raises ValueError on text that is not a valid integer literal. Per Session 11, this needs a try/except around it, or the program crashes on the very first bad keystroke.
Question 3 of 5

Why should raw user input be validated and converted BEFORE being passed to a method like set_population, rather than passing the raw string directly?

Comparing a string to an int with value < 0 raises a TypeError in Python. Converting and validating at the input boundary produces a clearer, more specific error message right where the bad data entered the program.
Question 4 of 5

In a text menu loop like while True: choice = input("> ")\n if choice == "quit": break, what happens if the user just presses Enter with no text?

An empty line from the user is still a valid (empty) string. It simply fails the == "quit" check like any other non-matching input, so the loop naturally continues and prompts again.
Question 5 of 5

What is the safest pattern for reading a required number from a user who might type invalid text?

Looping with try/except around the conversion lets you catch bad input, tell the user what went wrong, and re-prompt โ€” rather than crashing on the first typo, which is unacceptable for anything meant to be used interactively.

3. The Concept โ€” Reading and Validating User Input

INPUT()"fifty" (str)INT()ValueErrorcaughtSET_POPULATIONnever calledwith bad data

Raw text is validated and converted at the boundary, before it ever reaches a method expecting a number.

input() always returns a string

The built-in input() function pauses the program, waits for the user to type a line and press Enter, and returns exactly what they typed โ€” always as a string, never automatically converted.

name = input("Enter a country name: ")
print(type(name))  # <class 'str'> โ€” always, even if they typed "54000000"

Converting and validating before using the value

A numeric field like population needs explicit conversion, and that conversion can fail on bad input โ€” exactly the kind of case Session 11's try/except exists for.

raw = input("Enter population: ")

try:
    population = int(raw)
except ValueError:
    print(f"'{raw}' is not a valid number.")
    population = None

if population is not None:
    print("Parsed population:", population)

Connecting validated input to a controlled method

Once input is converted and confirmed valid, it can safely flow into the Session 21 methods, which add their own domain-specific validation (like rejecting negative numbers) on top.

def prompt_population(country):
    raw = input(f"New population for {country.name}: ")
    try:
        value = int(raw)
    except ValueError:
        print(f"'{raw}' is not a valid number. No change made.")
        return
    try:
        country.set_population(value)
        print("Updated.")
    except ValueError as e:
        print("Rejected:", e)

A simple menu loop

Combining a loop, input(), and conditional branching gives us an interactive text menu โ€” a small but real interactive program.

def run_menu(country):
    while True:
        choice = input("(g)row population, (s)how summary, (q)uit: ").strip().lower()
        if choice == "q":
            print("Goodbye.")
            break
        elif choice == "g":
            prompt_population(country)
        elif choice == "s":
            print(country.summary())
        else:
            print("Unrecognised option, try again.")

4. Lab

Lab objective: Build an interactive text menu that reads and validates user input, then applies it through the controlled state methods from Session 21.

What you will build

A file called input_lab.py.

Step-by-step instructions

1

Create the file with Country including set_population and grow_population

# input_lab.py
class Country:
    def __init__(self, name, region, population):
        self.name = name
        self.region = region
        self.population = population

    def summary(self):
        return f"{self.name} ({self.region}): pop. {self.population:,}"

    def set_population(self, value):
        if value < 0:
            raise ValueError(f"population must be non-negative, got {value}")
        self.population = value

    def grow_population(self, amount):
        if amount < 0:
            raise ValueError(f"amount must be non-negative, got {amount}")
        self.population += amount
2

Write a function that reads and validates a number from the user

def read_int(prompt):
    raw = input(prompt)
    try:
        return int(raw)
    except ValueError:
        print(f"'{raw}' is not a valid number.")
        return None
3

Write prompt_set_population using read_int and the controlled method

def prompt_set_population(country):
    value = read_int(f"New population for {country.name}: ")
    if value is None:
        return
    try:
        country.set_population(value)
        print("Updated:", country.summary())
    except ValueError as e:
        print("Rejected:", e)
4

Write the menu loop and run it

When you run this file directly, try typing an invalid number, then a negative number, then a valid one โ€” confirm all three cases behave correctly.

def run_menu(country):
    while True:
        choice = input("(g)row, (s)et, (v)iew, (q)uit: ").strip().lower()
        if choice == "q":
            print("Goodbye.")
            break
        elif choice == "s":
            prompt_set_population(country)
        elif choice == "g":
            amount = read_int("Amount to grow by: ")
            if amount is not None:
                try:
                    country.grow_population(amount)
                    print("Updated:", country.summary())
                except ValueError as e:
                    print("Rejected:", e)
        elif choice == "v":
            print(country.summary())
        else:
            print("Unrecognised option.")

if __name__ == "__main__":
    kenya = Country(name="Kenya", region="Africa", population=54000000)
    run_menu(kenya)
5

Confirm the __main__ guard from Session 10 is used correctly

The menu should only run when this file is executed directly, not if it were imported elsewhere.


5. Expected Files Changed

FileActionWhy
input_lab.py Created An interactive menu reading validated user input into controlled state methods.
docs/sessions/session-22/index.html Created This session document.
If you find yourself editing any other file, stop. This session touches exactly 2 files.

6. Commit Checkpoint

Once the lab is complete and you can explain every line, make this exact commit:

git add input_lab.py docs/sessions/session-22/index.html
git commit -m "session-22: read and validate user input, connect it to controlled state methods"
Do not commit until you can answer out loud: "Why does read_int need its own try/except, separate from set_population's own validation?"

7. Code Review Checklist

Go through your code line by line and check each item:


8. Post-Coding Quiz

Same 5 questions. Take it again now that you have written and run the code. You need 4/5 to mark this session complete.

Question 1 of 5

What is the type of the value returned by input("Enter population: "), regardless of what the user types?

input() always returns a string, no matter what characters the user typed. If you need a number, you must convert it yourself, typically with int() or float(), and handle the case where the conversion fails.
Question 2 of 5

A user types "fifty" when asked for a population. What happens if you call int(user_input) directly with no error handling?

int() raises ValueError on text that is not a valid integer literal. Per Session 11, this needs a try/except around it, or the program crashes on the very first bad keystroke.
Question 3 of 5

Why should raw user input be validated and converted BEFORE being passed to a method like set_population, rather than passing the raw string directly?

Comparing a string to an int with value < 0 raises a TypeError in Python. Converting and validating at the input boundary produces a clearer, more specific error message right where the bad data entered the program.
Question 4 of 5

In a text menu loop like while True: choice = input("> ")\n if choice == "quit": break, what happens if the user just presses Enter with no text?

An empty line from the user is still a valid (empty) string. It simply fails the == "quit" check like any other non-matching input, so the loop naturally continues and prompts again.
Question 5 of 5

What is the safest pattern for reading a required number from a user who might type invalid text?

Looping with try/except around the conversion lets you catch bad input, tell the user what went wrong, and re-prompt โ€” rather than crashing on the first typo, which is unacceptable for anything meant to be used interactively.

9. Reflection Questions

Think through these after the post-quiz. No right answer โ€” they are for discussion.

  1. Why are there two separate layers of validation here โ€” read_int's conversion check and set_population's value check? Could you combine them into one? Should you?
  2. What would happen if you skipped read_int entirely and called int(input(...)) directly inside prompt_set_population? Trace through what happens on bad input.
  3. How does this session's "validate at the boundary, then trust the value" pattern relate to what you learned about function contracts in Session 08 and 11?
  4. Where else in a real application (not just population) would you need this same read-validate-apply pattern for user input?

10. What Breaks If This Knowledge Is Missing?

  • Crash on first typo: Without read_int's try/except, the very first time a user makes a typo, the entire program crashes instead of gracefully asking again โ€” completely unacceptable for anything meant to be used by another person.
  • Tracing state changes (Session 23): The next session adds logging/debugging so you can trace exactly when and why state changed. Without a controlled input path like this session's, there would be too many uncontrolled entry points to trace meaningfully.
  • Real API responses (Layer 7): Session 42's real API data needs exactly this same "validate untrusted external input before trusting it" discipline โ€” user keyboard input and network responses are both untrusted data from outside your program's control.

11. What We Learned

Python concept mastered: Reading user input with input(), converting and validating it safely, and connecting it to controlled state-change methods via a menu loop.

Unlocks: You have built your first genuinely interactive program โ€” one that reacts safely to unpredictable human input instead of only running pre-written code.

Next session: Session 23 โ€” Tracing State Changes. We add visibility into exactly when and why state changes, using logging and print-based tracing.