Errors and Exceptions

What Are Errors?

Errors (also called exceptions) are problems that occur when your program runs. When Python encounters an error, it stops running and displays an error message.

Example:

age = int(input("Enter your age: "))
# If user types "twenty" instead of "20", Python crashes with an error!

Output:

Enter your age: twenty
ValueError: invalid literal for int() with base 10: 'twenty'

Without error handling, your program crashes and stops. With error handling, you can catch these errors and keep your program running.


← Back to where you were | → Start the course from the beginning


Common Types of Errors

ValueError

Happens when you try to convert something to the wrong type.

age = int("twenty")  # ValueError: can't convert "twenty" to integer
number = float("abc")  # ValueError: can't convert "abc" to float

TypeError

Happens when you use the wrong type of data for an operation.

result = "5" + 5  # TypeError: can't add string and integer
name = "Alice"
name[0] = "B"     # TypeError: strings are immutable

KeyError

Happens when you try to access a dictionary key that doesn’t exist.

flashcards = {"Dog": "Hund", "Cat": "Katze"}
translation = flashcards["Bird"]  # KeyError: 'Bird' doesn't exist

IndexError

Happens when you try to access a list index that doesn’t exist.

scores = [85, 92, 78]
score = scores[5]  # IndexError: list only has 3 items (indices 0-2)

FileNotFoundError

Happens when you try to open a file that doesn’t exist.

with open("missing_file.txt", "r") as file:
    content = file.read()  # FileNotFoundError: file doesn't exist

ZeroDivisionError

Happens when you try to divide by zero.

result = 10 / 0  # ZeroDivisionError: division by zero


← Back to where you were | → Start the course from the beginning


The try/except Pattern

The try/except pattern lets you catch errors and handle them gracefully instead of crashing.

Basic Syntax

try:
    # Code that might cause an error
    risky_code()
except:
    # What to do if ANY error occurs
    print("Something went wrong!")

How it works:

  1. Python tries to run the code in the try block
  2. If an error occurs, it jumps to the except block
  3. If no error occurs, the except block is skipped

Simple Example

# Without error handling - crashes!
age = int(input("Enter your age: "))
print(f"You are {age} years old")

# With error handling - doesn't crash!
try:
    age = int(input("Enter your age: "))
    print(f"You are {age} years old")
except:
    print("That's not a valid age!")

What happens:

Enter your age: twenty
That's not a valid age!
# Program continues running!


← Back to where you were | → Start the course from the beginning


Catching Specific Errors

It’s better to catch specific error types so you know exactly what went wrong.

Single Exception Type

try:
    age = int(input("Enter your age: "))
    print(f"You are {age} years old")
except ValueError:
    print("Please enter a number, not text!")

Multiple Exception Types

try:
    flashcards = {"Dog": "Hund", "Cat": "Katze"}
    word = input("Enter a word: ")
    translation = flashcards[word]
    print(f"{word} = {translation}")
except KeyError:
    print("That word isn't in the flashcards!")
except ValueError:
    print("Invalid input format!")

Catching Multiple Errors with One Block

try:
    result = 10 / int(input("Enter a number: "))
    print(result)
except (ValueError, ZeroDivisionError):
    print("Invalid input or division by zero!")


← Back to where you were | → Start the course from the beginning


The Else and Finally Clauses

Else - Runs if NO Error Occurs

try:
    age = int(input("Enter your age: "))
except ValueError:
    print("Invalid age!")
else:
    # Only runs if no error occurred
    print(f"You are {age} years old")

Finally - Always Runs (Error or Not)

try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("File not found!")
finally:
    # Always runs, even if there was an error
    print("Closing file operations")
    file.close()

Note: When using with statements, you don’t need finally for files because they close automatically.

Complete Pattern

try:
    # Try to do something risky
    result = risky_operation()
except SpecificError:
    # Handle specific error
    print("Specific error occurred")
else:
    # Only if no error
    print("Success!")
finally:
    # Always runs
    print("Cleanup complete")


← Back to where you were | → Start the course from the beginning


Practical Examples

Example 1: Safe Number Input

def get_number(prompt):
    """Keep asking until user enters a valid number."""
    while True:
        try:
            number = int(input(prompt))
            return number  # Success - return the number
        except ValueError:
            print("That's not a number! Try again.")

# Use it:
age = get_number("Enter your age: ")
print(f"You are {age} years old")

What happens:

Enter your age: twenty
That's not a number! Try again.
Enter your age: 25
You are 25 years old

Example 2: Safe Dictionary Access

flashcards = {"Dog": "Hund", "Cat": "Katze", "House": "Haus"}

word = input("Enter English word: ")

try:
    translation = flashcards[word]
    print(f"{word} = {translation}")
except KeyError:
    print(f"Sorry, '{word}' is not in the flashcards")
    print("Would you like to add it?")

Example 3: Safe File Loading

import json

def load_flashcards(filename):
    """Load flashcards from file, or return empty dict if file doesn't exist."""
    try:
        with open(filename, "r") as file:
            flashcards = json.load(file)
        print(f"✓ Loaded {len(flashcards)} flashcards")
        return flashcards
    except FileNotFoundError:
        print("No flashcard file found. Starting fresh!")
        return {}
    except json.JSONDecodeError:
        print("Flashcard file is corrupted. Starting fresh!")
        return {}

# Use it:
flashcards = load_flashcards("flashcards.json")

Example 4: Division Calculator

def safe_divide(a, b):
    """Divide two numbers safely."""
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        print("Error: Cannot divide by zero!")
        return None
    except TypeError:
        print("Error: Both inputs must be numbers!")
        return None

# Use it:
print(safe_divide(10, 2))     # 5.0
print(safe_divide(10, 0))     # Error message, returns None
print(safe_divide("10", 2))   # Error message, returns None

Example 5: Flashcard Practice with Error Handling

import json
import random

def load_flashcards():
    """Load flashcards with error handling."""
    try:
        with open("flashcards.json", "r") as file:
            flashcards = json.load(file)
        
        if not flashcards:
            print("Flashcard file is empty!")
            return {}
        
        return flashcards
    
    except FileNotFoundError:
        print("No flashcards found. Please add some first!")
        return {}
    except json.JSONDecodeError:
        print("Flashcard file is corrupted!")
        return {}

def practice_flashcards():
    """Practice flashcards with error handling."""
    flashcards_list = load_flashcards()
    
    if not flashcards:
        print("No flashcards to practice!")
        return
    
    # Practice loop
    random.shuffle(flashcard_list)
    
    score = 0
    
    for english, german in flashcard_list:
        print(f"\nTranslate: {english}")
        user_answer = input("Your answer: ").strip()
        
        if user_answer.lower() == german.lower():
            print("✓ Correct!")
            score += 1
        else:
            print(f"✗ Wrong! The answer was: {german}")
    
    # Display results
    total = len(flashcard_list)
    percentage = (score / total) * 100
    print(f"\n--- Results ---")
    print(f"Score: {score}/{total} ({percentage:.1f}%)")

# Run the practice
practice_flashcards()


← Back to where you were | → Start the course from the beginning

Best Practices

✅ DO: Catch Specific Errors

# Good - clear what error you're handling
try:
    age = int(input("Age: "))
except ValueError:
    print("Please enter a number")

❌ DON’T: Catch All Errors Blindly

# Bad - hides what went wrong
try:
    age = int(input("Age: "))
except:
    print("Something went wrong")  # What went wrong?

✅ DO: Provide Helpful Error Messages

# Good - user knows what to do
except ValueError:
    print("Please enter a valid number (digits only)")

❌ DON’T: Use Silent Failures

# Bad - error is hidden, user confused
try:
    age = int(input("Age: "))
except:
    pass  # Error is completely hidden!

✅ DO: Keep Try Blocks Small

# Good - only risky code in try block
user_input = input("Enter age: ")
try:
    age = int(user_input)
except ValueError:
    print("Invalid age")
else:
    print(f"You are {age}")

❌ DON’T: Put Too Much in Try Blocks

# Bad - hard to know what caused the error
try:
    user_input = input("Enter age: ")
    age = int(user_input)
    print(f"You are {age}")
    next_age = age + 1
    print(f"Next year: {next_age}")
except:
    print("Something went wrong")  # What exactly?

When to Use Error Handling

Use try/except when:

  • ✅ Converting user input to numbers
  • ✅ Opening/reading files
  • ✅ Accessing dictionary keys
  • ✅ Working with external data (JSON, CSV)
  • ✅ Performing calculations that might fail
  • ✅ Any operation that could fail for reasons outside your control

Don’t use try/except for:

  • ❌ Logic errors in your code (fix the bug instead!)
  • ❌ Checking if a variable exists (use if statements)
  • ❌ Hiding programming mistakes


← Back to where you were | → Start the course from the beginning

Common Patterns

Pattern 1: Input Validation Loop

while True:
    try:
        num = int(input("Enter a number: "))
        break  # Success - exit loop
    except ValueError:
        print("Invalid! Try again.")

Pattern 2: Safe Dictionary Get

# Option 1: try/except
try:
    value = my_dict[key]
except KeyError:
    value = default_value

# Option 2: .get() method (simpler!)
value = my_dict.get(key, default_value)

Pattern 3: File Loading with Fallback

try:
    with open("data.json", "r") as file:
        data = json.load(file)
except (FileNotFoundError, json.JSONDecodeError):
    data = {}  # Use empty dict as fallback

Pattern 4: Multiple Attempts

max_attempts = 3
attempts = 0

while attempts < max_attempts:
    try:
        age = int(input("Enter age: "))
        break
    except ValueError:
        attempts += 1
        print(f"Invalid! {max_attempts - attempts} attempts left")

if attempts == max_attempts:
    print("Too many failed attempts!")

Debugging Tip: Print Error Details

Sometimes you want to see the actual error message:

try:
    result = risky_operation()
except Exception as e:
    print(f"Error occurred: {e}")
    print(f"Error type: {type(e).__name__}")

Example:

try:
    age = int("twenty")
except Exception as e:
    print(f"Error: {e}")
    # Output: Error: invalid literal for int() with base 10: 'twenty'
    print(f"Type: {type(e).__name__}")
    # Output: Type: ValueError


← Back to where you were | → Start the course from the beginning

Summary

Key takeaways:

  1. Errors crash programs - use try/except to prevent this
  2. Try/except pattern:
    try:
        # Risky code
    except SpecificError:
        # Handle error
    
  3. Catch specific errors - not all errors blindly
  4. Provide helpful messages - tell users what went wrong
  5. Keep try blocks small - easier to debug
  6. Common use cases - user input, files, dictionary access

Most common errors for beginners:

  • ValueError - Invalid conversions
  • KeyError - Missing dictionary keys
  • IndexError - Invalid list indices
  • FileNotFoundError - Missing files
  • TypeError - Wrong data types


← Back to where you were | → Start the course from the beginning