Day 8 of 80

Error Handling & Debugging

Phase 1: Python Foundation

Here is the comprehensive content for Day 8.

*

What You'll Build Today

Welcome to Day 8. Up until now, we have lived in a perfect world where users always type exactly what we want, calculations never fail, and the internet always works.

Today, we face reality. In the real world, code breaks. Users type "five" instead of "5". Files go missing. Servers go offline. If your Python script encounters an error it doesn't expect, it crashes immediately. The screen turns red, the program stops, and the user is left confused.

Today, you will build a Crash-Proof Calculator. It will be robust, resilient, and polite. Instead of crashing when things go wrong, it will catch the error, explain the problem to the user, and ask them to try again.

Here is what you will master today:

* The try and except blocks: The safety net that catches errors before they crash your program.

* Specific Exception Handling: How to treat a "division by zero" error differently than a "user typed garbage" error.

* Reading Stack Traces: How to read that scary wall of red text to find exactly where your code broke.

* Debugging Logic: Using the VS Code debugger to pause time and inspect your variables while the code runs.

Let's make your code unbreakable.

The Problem

To understand why error handling is vital, we first need to see what happens without it.

Imagine you have written a simple script to calculate the return on investment (ROI) for a user. You ask for their profit and their cost, and you divide the two.

Create a file named fragile_calc.py and run this code:

print("--- ROI Calculator ---")

profit = input("Enter your profit: ")

cost = input("Enter your cost: ")

# Convert strings to numbers

profit_num = float(profit)

cost_num = float(cost)

roi = profit_num / cost_num

print(f"Your ROI is: {roi}")

print("Calculation complete. Have a nice day!")

Scenario 1: The Happy Path

Run the code. Enter 100 for profit and 50 for cost.

Result: Your ROI is: 2.0. It works perfectly.

Scenario 2: The Unexpected Input

Run the code again.

Enter 100 for profit.

Enter zero for cost (maybe you just forgot to type the number).

The Crash:

You will see something like this:

Traceback (most recent call last):

File "fragile_calc.py", line 8, in

cost_num = float(cost)

ValueError: could not convert string to float: 'zero'

Notice two painful things:

  • The program crashed. The line print("Calculation complete...") never ran. The application died instantly.
  • The user experience is terrible. If this were a web app, the user would see a blank screen or a cryptic error code.
  • Scenario 3: The Mathematical Impossibility

    Run the code again.

    Enter 100 for profit.

    Enter 0 for cost.

    The Crash:
    ZeroDivisionError: float division by zero
    

    Again, the program dies.

    In a GenAI context, this is a disaster. If you are paying for an expensive AI API call and your code crashes halfway through processing the result because of a minor formatting error, you lose data and money. We need a way to say, "If an error happens, don't die—just handle it."

    Let's Build It

    We are going to rebuild this calculator using Exception Handling. We will wrap our dangerous code in a protective bubble.

    Step 1: The Basic "try/except" Block

    The syntax for handling errors in Python is try and except.

    * try: "Attempt to run this code."

    * except: "If an error happens in the try block, stop immediately and run this code instead."

    Create a new file called robust_calc.py:

    print("--- Robust Calculator 1.0 ---")
    
    

    try:

    # This is the "Dangerous" zone where errors might happen

    numerator = float(input("Enter the top number: "))

    denominator = float(input("Enter the bottom number: "))

    result = numerator / denominator

    print(f"The result is: {result}")

    except:

    # This block only runs if SOMETHING goes wrong above

    print("Something went wrong! Please check your inputs.")

    print("Program finished successfully.")

    Run this code. Try entering zero or dividing by 0.

    Instead of a crash and a red error message, you get:

    Something went wrong! Please check your inputs. Program finished successfully.

    The program didn't die! It continued to the final print statement.

    Step 2: Handling Specific Errors

    The code above is a good start, but it's vague. "Something went wrong" doesn't tell the user what they did wrong. Did they divide by zero? Did they type a letter?

    We can catch specific errors by name.

    Update robust_calc.py:

    print("--- Robust Calculator 2.0 ---")
    
    

    try:

    numerator = float(input("Enter the top number: "))

    denominator = float(input("Enter the bottom number: "))

    result = numerator / denominator

    print(f"The result is: {result}")

    except ValueError:

    # This runs only if the conversion to float fails (e.g., user typed "abc")

    print("Error: You must enter a number, not text.")

    except ZeroDivisionError:

    # This runs only if they try to divide by 0

    print("Error: You cannot divide by zero.")

    except Exception as e:

    # This catches any other unexpected error and prints the system message

    print(f"An unexpected error occurred: {e}")

    print("Program finished.")

    Why this is better:
  • If they type "hello", Python matches the ValueError block and gives a specific tip.
  • If they type "0", Python matches the ZeroDivisionError block.
  • The final except Exception as e is a catch-all bucket for errors we didn't predict, preventing the crash while still showing us what happened.
  • Step 3: The "else" and "finally" Blocks

    Python error handling has two other useful keywords:

    * else: Runs only if the try block succeeded (no errors occurred).

    * finally: Runs no matter what, whether there was an error or not. This is used for cleanup (like closing a file or database connection).

    Let's add these to our calculator to make it professional.

    print("--- Robust Calculator 3.0 ---")
    
    

    try:

    numerator = float(input("Enter the top number: "))

    denominator = float(input("Enter the bottom number: "))

    result = numerator / denominator

    except ValueError:

    print("Error: Please enter valid numbers.")

    except ZeroDivisionError:

    print("Error: Division by zero is not allowed.")

    else:

    # This only runs if the calculation succeeded

    print(f"Success! {numerator} / {denominator} = {result}")

    finally:

    # This always runs

    print("--- Operation Closed ---")

    Step 4: Debugging with Print vs. Debugger

    Sometimes your code doesn't crash, but it gives the wrong answer. This is a "logic error."

    The Old Way: Print Debugging

    You sprinkle print(variable_name) all over your code to see what's happening.

    * print(f"DEBUG: numerator is {numerator}")

    * print("DEBUG: Inside the if-statement")

    This works, but it's messy. You have to delete all those lines later.

    The Pro Way: VS Code Debugger

    VS Code has a built-in tool that lets you pause your code while it runs.

  • Look at your code in VS Code.
  • Move your mouse to the left of the line number where you calculate result.
  • You will see a faint red dot. Click it. It becomes a bright red dot. This is a Breakpoint.
  • Press F5 (or go to Run -> Start Debugging) and select "Python File".
  • What happens:
  • The program asks for inputs in the terminal. Enter them.
  • The program freezes at your red dot.
  • On the left side of VS Code, look at the Variables pane. You can see the current values of numerator and denominator exactly as the computer sees them.
  • At the top center, a toolbar appears. Click the "Step Over" arrow (curved arrow jumping over a dot) to run the code one line at a time.
  • This allows you to watch your logic flow step-by-step without writing a single print statement.

    Step 5: Reading the Stack Trace (The Detective Work)

    When a crash does happen (and you haven't handled it), Python prints a Stack Trace (or Traceback). Beginners find this scary. Experts love it because it is a treasure map.

    Let's look at a crash again:

    Traceback (most recent call last):
    

    File "/Users/you/code/day8/calc.py", line 4, in

    result = 10 / 0

    ZeroDivisionError: division by zero

    How to read it:
  • Read the Bottom Line First: This tells you what happened. (ZeroDivisionError: division by zero).
  • Read the File Path: It tells you where it happened (calc.py).
  • Read the Line Number: It tells you exactly where to look (line 4).
  • Never ignore the traceback. It usually points exactly to the typo or logic error.

    Now You Try

    You have a calculator that handles basic math errors. Now, let's expand it.

    1. The Loop of Persistence

    Currently, if the user makes a mistake, the program prints an error and ends. Modify the code to use a while True loop so that it keeps asking the user for input until they get it right.

    Hint:* Put the try/except inside the loop. Use break in the else block (because else means it worked!). 2. The Dictionary Lookup

    Create a small dictionary of currency exchange rates: rates = {'USD': 1.0, 'EUR': 0.85, 'GBP': 0.75}.

    Ask the user to type a currency code (e.g., "JPY").

    Try to access the dictionary.

    Catch the KeyError (which happens when a key isn't found) and print "Sorry, we don't support that currency."

    3. The Type Protector

    Write a function that asks for a user's age. If they enter a number less than 0 or greater than 120, raise your own error using the keyword raise ValueError("Age must be realistic"). Then catch that error in your except block.

    Challenge Project: The "Chaos Script"

    Your goal is to write a script that is designed to fail, but handles the failure so gracefully that the user never knows it crashed.

    Requirements:
  • Create a list of data: data_list = ["Python", "AI", "Code"].
  • Create a dictionary of user info: user_info = {"name": "Alice", "role": "Admin"}.
  • Ask the user for an input to select a "Chaos Mode" (1, 2, or 3).
  • Mode 1: Try to print the 10th item in data_list. This will cause an IndexError. Catch it and print "That item doesn't exist."
  • Mode 2: Try to print user_info["age"]. This will cause a KeyError. Catch it and print "User age not found."
  • Mode 3: Try to add user_info["name"] + 5. This will cause a TypeError (adding text to a number). Catch it and print "Cannot do math with words."
  • Wrap the whole thing in a loop so the user can try different modes.
  • Example Output:
    Select Chaos Mode (1-3): 1
    

    Error caught: Our list isn't that long! (IndexError handled)

    Select Chaos Mode (1-3): 2

    Error caught: Missing data field. (KeyError handled)

    Select Chaos Mode (1-3): 5

    Please enter a number between 1 and 3.

    Hints:

    * You will need multiple except blocks, one for each error type (IndexError, KeyError, TypeError).

    * Remember that accessing a list index that is too high causes IndexError.

    What You Learned

    Today you moved from writing "happy path" code to writing "production-ready" code.

    * try/except: You learned to anticipate failure and handle it safely.

    * Specific Exceptions: You learned that ValueError is different from ZeroDivisionError, and you handled them differently.

    * Tracebacks: You learned to read the red error text from the bottom up to locate bugs.

    * Debugging: You learned that you can pause code execution to inspect variables.

    Why This Matters for AI:

    When you start building AI agents, you will be connecting to OpenAI, Google, or Anthropic APIs. These connections fail. Sometimes the AI returns invalid JSON. Sometimes the server times out. If your script crashes every time the internet blinks, your AI application is useless. Error handling is the difference between a toy and a tool.

    Tomorrow:

    Now that your code is robust, we need to make it memorable. Tomorrow, we tackle File I/O. You will learn how to read files, write data to disk, and ensure your program "remembers" things even after you turn off your computer.