Lesson 10 • Intermediate
Exception Handling in Python
Learn to detect, catch, and manage errors gracefully so your programs never crash unexpectedly.
What You'll Learn in This Lesson
- ✓What exceptions are and why programs crash without handling
- ✓How to use try / except to catch errors gracefully
- ✓How to catch specific exception types (ValueError, KeyError, etc.)
- ✓How to capture the error message with as e
- ✓The else and finally blocks and when to use them
- ✓How to raise your own exceptions with raise
What Are Exceptions?
When something goes wrong in your Python code — like dividing by zero or opening a missing file — Python raises an exception. Without handling, your program crashes.
See an Exception
Run this to see what an unhandled exception looks like
# This code will crash without exception handling
result = 10 / 0 # ZeroDivisionError!
print("This line never runs")Why Handle Exceptions?
| Without Exception Handling | With Exception Handling |
|---|---|
| Program crashes completely | Program continues running |
| Confusing error messages | User-friendly messages |
| Data might be lost | Data is saved safely |
| Resources left open | Resources cleaned up properly |
The try-except Block
Wrap risky code in a try block. If an error occurs, the except block handles it.
| Syntax | What It Does |
|---|---|
| try: | Code that might fail goes here |
| except: | Runs if an error occurs in try |
Basic Try-Except
Catch any error
try:
result = 10 / 0
print("Result:", result)
except:
print("Oops! Something went wrong.")
print("Program continues running!")except: catches ALL errors, even ones you didn't expect. This can hide bugs!Catching Specific Exceptions
Always catch specific exception types. This is the professional way.
Catch Specific Exception
Catch only ValueError
# Catching a specific exception type
try:
number = int("hello") # Can't convert "hello" to a number
except ValueError:
print("That's not a valid number!")
# This only catches ValueError, not other errorsCommon Exception Types
Here are the exceptions you'll encounter most often:
| Exception | When It Happens | Example |
|---|---|---|
| ValueError | Wrong value type | int("abc") |
| TypeError | Wrong operation for type | "5" + 3 |
| ZeroDivisionError | Dividing by zero | 10 / 0 |
| IndexError | Invalid list index | my_list[100] |
| KeyError | Missing dictionary key | my_dict["name"] |
| FileNotFoundError | File doesn't exist | open("missing.txt") |
| NameError | Variable not defined | print(xyz) |
Different Exception Types
See IndexError and KeyError
# Try different exceptions
my_list = [1, 2, 3]
try:
print(my_list[10]) # IndexError
except IndexError:
print("Index out of range!")
# Try this with a dictionary
my_dict = {"name": "Alice"}
try:
print(my_dict["age"]) # KeyError
except KeyError:
print("Key not found!")Catching Multiple Exceptions
You can handle different exceptions differently:
Multiple Except Blocks
Handle different errors differently
user_input = "abc" # Try changing to "0" or "5"
try:
number = int(user_input)
result = 100 / number
print("Result:", result)
except ValueError:
print("Please enter a valid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
print("Program continues...")Or handle them together if the response is the same:
Group Exceptions
Handle multiple types the same way
try:
risky = 10 / int("zero")
except (ValueError, ZeroDivisionError):
print("Invalid input - please try again!")Getting the Error Message
Use as to capture the error details:
Capture Error Message
Get details about what went wrong
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error occurred: {e}")
# e contains the error message
# This is helpful for logging and debuggingThe else Block
The else block runs only if NO exception occurred:
| Block | When It Runs |
|---|---|
| try: | Always tries to run |
| except: | Only if error in try |
| else: | Only if NO error in try |
The else Block
Code that runs on success
number = "5" # Try changing to "abc"
try:
result = int(number)
except ValueError:
print("Invalid number!")
else:
print(f"Success! You entered: {result}")
print(f"Doubled: {result * 2}")The finally Block
The finally block always runs, whether there was an error or not. Perfect for cleanup.
The finally Block
Code that always runs
print("Starting operation...")
try:
result = 10 / 2 # Try changing to 10 / 0
print(f"Result: {result}")
except ZeroDivisionError:
print("Cannot divide by zero!")
finally:
print("Cleanup complete!") # Always runs
print("Done!")The Complete Structure
All four blocks together:
try:
# Risky code here
except SomeError:
# Handle the error
else:
# Runs if no error
finally:
# Always runs (cleanup)Complete Try Structure
try-except-else-finally
filename = "data.txt"
try:
# Simulate file operation
print(f"Trying to open {filename}...")
# file = open(filename) # Would cause FileNotFoundError
data = "Sample data" # Simulated success
except FileNotFoundError:
print("File not found!")
data = None
else:
print("File read successfully!")
print(f"Data: {data}")
finally:
print("Operation complete - resources cleaned up.")Raising Exceptions
Use raise to trigger an exception yourself. This is useful for validating data.
Raise Your Own Exceptions
Trigger errors for invalid data
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative!")
if age > 150:
raise ValueError("Age seems unrealistic!")
return age
# Test it
try:
my_age = set_age(-5) # Try 25 or 200
except ValueError as e:
print(f"Error: {e}")Common Mistakes to Avoid
| Mistake | Problem | Fix |
|---|---|---|
| except: | Catches everything, hides bugs | except ValueError: |
| Large try block | Hard to know what failed | Put only risky code in try |
| Empty except block | Error is silently ignored | At least log the error |
| Catching too high | except Exception catches all | Be specific about types |
| Not cleaning up | Files/connections left open | Use finally or with |
Good vs Bad Exception Handling
See why specific exceptions are better
# BAD - Catches everything, hides bugs
try:
result = 10 / my_variable # NameError hidden!
except:
print("Something went wrong")
# GOOD - Specific exceptions
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
# NameError will still crash (and we'll notice the bug!)Practical Example: Safe Number Input
A function that safely gets a number from user input:
Safe Number Input
Handle invalid user input gracefully
def get_number(prompt, default=0):
"""Safely get a number, return default if invalid."""
user_input = input(prompt) # In practice, this would be real input
try:
return float(user_input)
except ValueError:
print(f"Invalid input, using default: {default}")
return default
# Simulate with different inputs
test_inputs = ["42", "3.14", "hello", ""]
for test in test_inputs:
print(f"Input: '{test}'")
try:
result = float(test) if test else 0
...Practical Example: Calculator
A safe calculator that handles errors:
Safe Calculator
Calculator with full error handling
def safe_calculate(a, b, operation):
"""Perform calculation with error handling."""
try:
if operation == "+":
return a + b
elif operation == "-":
return a - b
elif operation == "*":
return a * b
elif operation == "/":
return a / b
else:
raise ValueError(f"Unknown operation: {operation}")
except ZeroDivisionError:
return "Error: Cannot divide by zero"
except TypeError:
...Practical Example: Data Processor
Process a list of data, handling errors without stopping:
Robust Data Processing
Skip bad data, keep processing
def process_data(data_list):
"""Process each item, skip invalid ones."""
results = []
errors = []
for i, item in enumerate(data_list):
try:
# Try to convert to number and double it
result = float(item) * 2
results.append(result)
except (ValueError, TypeError) as e:
errors.append(f"Item {i}: {item} - {e}")
return results, errors
# Test with mixed data
data = ["10", "5.5", "hello", "20", None, "15"]
resu
...Summary: Quick Reference
| Keyword | Purpose | Example |
|---|---|---|
| try: | Code that might fail | try: x = 1/0 |
| except Type: | Handle specific error | except ValueError: |
| except Type as e: | Capture error message | except ValueError as e: |
| else: | Runs if no error | else: print("OK") |
| finally: | Always runs (cleanup) | finally: file.close() |
| raise | Trigger an exception | raise ValueError("!") |
Key Takeaways:
- • Always catch specific exception types
- • Use finally for cleanup that must always happen
- • Use else for code that should only run on success
- • Raise exceptions early when you detect invalid data
- • Never use bare
except:in production code
What's Next?
You now know how to protect your programs from crashing and handle errors gracefully. Exception handling is essential for building reliable, professional Python applications.
Next up: Lesson 11 – Modules and Packages — Learn how to organize your code into reusable modules and use Python's powerful standard library.
Lesson 10 done — your programs are now crash-proof!
try/except, else, finally, raise — you know the full exception handling toolkit. This is what separates fragile scripts from professional, production-ready code.
🚀 Up next: Modules & Packages — discover Python's vast library ecosystem and learn to organise your own code into reusable files.
Sign up for free to track which lessons you've completed and get learning reminders.