Error Handling & Debugging
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.
Run the code again.
Enter 100 for profit.
Enter zero for cost (maybe you just forgot to type the number).
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:
print("Calculation complete...") never ran. The application died instantly.Run the code again.
Enter 100 for profit.
Enter 0 for cost.
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:
ValueError block and gives a specific tip.ZeroDivisionError block.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 DebuggingYou 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 DebuggerVS Code has a built-in tool that lets you pause your code while it runs.
result.numerator and denominator exactly as the computer sees them.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:
ZeroDivisionError: division by zero).calc.py).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 PersistenceCurrently, 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.
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."
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:data_list = ["Python", "AI", "Code"].user_info = {"name": "Alice", "role": "Admin"}.data_list. This will cause an IndexError. Catch it and print "That item doesn't exist."user_info["age"]. This will cause a KeyError. Catch it and print "User age not found."user_info["name"] + 5. This will cause a TypeError (adding text to a number). Catch it and print "Cannot do math with words."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.