Day 10: Exception Handling & Debugging

🐍 Day 10: Exception Handling & Debugging


1. Learning Objectives

By the end of Day 10, you will be able to:

  • Understand what exceptions are and why they occur

  • Use try, except, else, and finally to handle errors gracefully

  • Catch specific exceptions vs. generic ones

  • Raise your own exceptions with raise

  • Read and understand traceback messages to pinpoint bugs

  • Apply basic debugging techniques (print, assert, logging overview)

  • Write programs that fail gracefully instead of crashing


2. Concept Explanation

2.1 Why Exception Handling? — Programs Will Fail

No matter how carefully you code, errors happen. Files are missing, users type nonsense, networks go down. Without exception handling, your program crashes. With it, your program can respond gracefully and keep running.

# ❌ Without handling — crashes on bad input
age = int(input("Age: "))   # User types "twenty" → ValueError crash

# ✅ With handling — graceful response
try:
   age = int(input("Age: "))
except ValueError:
   print("Please enter a valid number.")

2.2 The try / except Block

The fundamental structure:

try:
   # Code that might raise an exception
   result = 10 / 0
except ZeroDivisionError:
   # Runs only if ZeroDivisionError occurs in try block
   print("You can't divide by zero!")

How it works:

  1. Python runs the try block.

  2. If an exception occurs, Python jumps to the first except that matches the exception type.

  3. If no exception occurs, the except block is skipped.

  4. The program continues after the try/except block.


2.3 Catching Specific Exceptions (Best Practice)

Never use a bare except — it catches everything, even KeyboardInterrupt (Ctrl+C) and system exits, which you usually want to allow.

# ❌ Bad — catches everything, hides real bugs
try:
   risky_code()
except:
   pass

# ✅ Good — catches only what you expect
try:
   num = int(input("Number: "))
   print(10 / num)
except ValueError:
   print("That's not an integer!")
except ZeroDivisionError:
   print("Cannot divide by zero.")

Common built-in exceptions:

Exception When It Occurs
ValueError Bad conversion: int("hello")
TypeError Wrong type: "hello" + 5
ZeroDivisionError Division by zero: 10 / 0
FileNotFoundError File doesn't exist: open("missing.txt")
IndexError List index out of range: [1,2][5]
KeyError Missing dictionary key: {}["key"]
AttributeError Object has no such attribute: "str".not_a_method()
NameError Variable not defined: print(x) before x = 1

2.4 The else and finally Clauses

else runs only if no exception occurred:

try:
   num = int(input("Number: "))
except ValueError:
   print("Not a number!")
else:
   print(f"Success! You entered {num}.")

finally runs no matter what — exception or not. Perfect for cleanup (closing files, releasing resources):

file = None
try:
   file = open("data.txt", "r")
   content = file.read()
except FileNotFoundError:
   print("File not found.")
finally:
   if file:
       file.close()    # Always runs, even if error occurred
   print("Cleanup done.")

💡 With with open(...) as f:, you don't need finally for files — with handles cleanup automatically. But finally is essential for other resources (database connections, network sockets, etc.).

Order of execution:

try → [except if error] → [else if no error] → finally (always)

2.5 Raising Your Own Exceptions — raise

When your code detects an invalid state, you can deliberately raise an exception:

def set_age(age):
   if age < 0:
       raise ValueError("Age cannot be negative.")
   print(f"Age set to {age}.")

# set_age(-5) # ValueError: Age cannot be negative.

Re-raising exceptions — catch an exception, do something (like logging), then re-raise it:

try:
   result = 10 / 0
except ZeroDivisionError:
   print("Logging this error...")
   raise   # Re-raises the original exception

2.6 Understanding Tracebacks

When an unhandled exception occurs, Python prints a traceback — a stack trace showing exactly where the error happened:

Traceback (most recent call last):
File "app.py", line 4, in <module>
result = 10 / number
ZeroDivisionError: division by zero

How to read a traceback:

  1. Read from bottom to top. The last line tells you what exception occurred and its message.

  2. Look at the file and line number right above it for the exact location.

  3. If multiple function calls are involved, the traceback shows the entire call stack — start at the top for the root cause.


2.7 Basic Debugging Techniques

1. Print debugging (the most straightforward):

def calculate(x, y):
print(f"DEBUG: x={x}, y={y}") # See what's coming in
result = x / y
print(f"DEBUG: result={result}")
return result

2. assert statements — check conditions during development:

def calculate_average(grades):
assert len(grades) > 0, "grades list is empty"
return sum(grades) / len(grades)

When an assert fails, it raises AssertionError. Assertions can be disabled with the -O flag for production, so never use them for actual validation — use if/raise for that.

3. pdb — Python Debugger (quick overview):

import pdb

def buggy_function(x, y):
pdb.set_trace() # Program pauses here; interactive debugger starts
return x / y

In pdb, you can inspect variables (p x), step through lines (n), continue (c), etc. We'll explore more later, but know it exists.

4. logging module (preview for advanced use):

import logging
logging.basicConfig(level=logging.DEBUG)

logging.debug("This is debug info")
logging.info("Program started")
logging.warning("This is a warning")
logging.error("An error occurred")

2.8 Common Mistakes & Anti-Patterns

Mistake Why It's Harmful
Bare except: Hides Ctrl+C and unexpected errors; makes debugging impossible
Catching too broadly except Exception: may swallow critical errors you want to see
Catching BaseException It includes SystemExit and KeyboardInterrupt, which you shouldn't suppress
pass inside except Silently swallows errors — you'll never know something went wrong
Not logging the original exception Always print or log the traceback

3. Code Examples

Example 1: Robust Number Input

def get_positive_float(prompt):
"""Keep asking until the user enters a valid positive float."""
while True:
try:
value = float(input(prompt))
if value <= 0:
print("Please enter a positive number.")
continue
return value
except ValueError:
print("That's not a valid number. Try again.")

price = get_positive_float("Enter price: $")
print(f"Price: ${price:.2f}")

Example 2: Safe File Reader

def read_file_safely(filename):
"""Read a file, handling missing file gracefully."""
try:
with open(filename, "r") as f:
return f.read()
except FileNotFoundError:
print(f"Error: '{filename}' not found.")
return None
except PermissionError:
print(f"Error: Permission denied for '{filename}'.")
return None

content = read_file_safely("config.cfg")
if content:
print(content)

Example 3: Custom Exception Class

class InsufficientFundsError(Exception):
"""Raised when a withdrawal exceeds the balance."""
pass

def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(
f"Cannot withdraw ${amount:.2f}. Balance: ${balance:.2f}"
)
return balance - amount

try:
new_balance = withdraw(100, 150)
except InsufficientFundsError as e:
print(e)

4. Hands-On Exercises

Exercise 1: Safe Division

Write a function safe_divide(a, b) that returns a / b. Handle ZeroDivisionError and TypeError (if non-numbers are passed). Return None on error and print a helpful message.

Exercise 2: List Index Accessor

Ask the user for an index, then print the element at that index from a predefined list ["Python", "Java", "C++", "JavaScript"]. Handle IndexError and ValueError gracefully.

Exercise 3: File Existence Checker

Write a function that asks for a filename. Try to open and read it. Handle FileNotFoundError by asking the user if they want to create the file. If yes, create it and write "Created by Python".

Exercise 4: Password Validator (with exceptions)

Write a function validate_password(pw) that:

  • Raises ValueError if password is shorter than 8 characters

  • Raises ValueError if password has no


Comments

Popular posts from this blog

Day 1: Welcome to Python — Your Journey Begins

Python: Your Gateway to Coding Adventures

Day 11: List Comprehensions & Lambda Functions

Earn From the Comfort of Your Home

Build the skills to work comfortably from home, on your own terms.

Get Started Today