
Python
Learn patterns for handling multiple different exceptions that can occur in a single operation.
When different errors need different handling, use separate except blocks:
# Reading and parsing a file - multiple things can go wrong
def load_config_file(filename):
try:
with open(filename) as f:
content = f.read()
config = json.loads(content)
return config
except FileNotFoundError:
print(f"Configuration file '{filename}' not found.")
return None
except json.JSONDecodeError as e:
print(f"Invalid JSON in '{filename}': {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
# Test with different failures
result = load_config_file("missing.json") # FileNotFoundError handling
result = load_config_file("invalid.json") # JSONDecodeError handlingWhen multiple exceptions need the same handling, group them:
# Tuple syntax for multiple exceptions
def safe_input_to_number(prompt):
try:
user_input = input(prompt)
number = int(user_input)
return number
except (ValueError, TypeError):
print("Please enter a valid number!")
return None
# Both ValueError and TypeError get the same message
# Real-world example: accessing nested data
def get_user_age(users_dict, user_id):
try:
user = users_dict[user_id]
age = int(user["age"])
return age
except (KeyError, TypeError):
print(f"Invalid user data for ID {user_id}")
return None
except ValueError:
print(f"Age field must be a number for ID {user_id}")
return None
# Test
users = {
1: {"name": "Alice", "age": "25"},
2: {"name": "Bob"},
}
print(get_user_age(users, 1)) # ValueError on age
print(get_user_age(users, 2)) # KeyError on missing age
print(get_user_age(users, 3)) # KeyError on missing userCatch specific exceptions before general ones:
# ❌ WRONG - too general first
try:
value = int(data["age"])
except Exception as e: # Catches EVERYTHING!
print("An error occurred") # Never reaches the specific handlers
except KeyError:
print("Age not found") # Unreachable!
except ValueError:
print("Age not a number") # Unreachable!
# ✅ CORRECT - specific to general
try:
value = int(data["age"])
except KeyError:
print("Age field not found")
except ValueError:
print("Age must be a number")
except Exception as e:
print(f"Unexpected error: {e}")# Processing records from a file with multiple error types
def process_student_records(filename):
"""Process student data from CSV file"""
students = []
try:
with open(filename, encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
try:
parts = line.strip().split(",")
name = parts[0]
age = int(parts[1])
grade = float(parts[2])
if age < 5 or age > 25:
raise ValueError(f"Invalid age: {age}")
students.append({
"name": name,
"age": age,
"grade": grade
})
except ValueError as e:
print(f"Line {line_num}: {e}")
except IndexError:
print(f"Line {line_num}: Missing fields")
except FileNotFoundError:
print(f"File not found: {filename}")
except UnicodeDecodeError:
print(f"File encoding error: {filename}")
return students
# Usage
students = process_student_records("students.csv")# Instead of catching many specific exceptions, catch parent
def fetch_data_from_api(endpoint):
try:
import requests
response = requests.get(endpoint, timeout=5)
return response.json()
except requests.RequestException as e:
# Catches: ConnectionError, Timeout, HTTPError, etc.
print(f"API request failed: {e}")
return None
# Without parent:
# except (requests.ConnectionError, requests.Timeout,
# requests.HTTPError, requests.RequestException):
# print("API error")
# Also works with built-in exceptions
def process_numeric_data(data):
try:
result = float(data) * 100
return result
except (ValueError, TypeError):
# Both inherit from same parent, but we catch both specifically
print("Data must be numeric")
return None
# Or catch parent:
except (ValueError, TypeError):
print("Cannot process data")
return None# Sometimes you catch one error but want to raise another
def database_operation(query):
try:
# Simulate database call
result = execute_query(query)
return result
except ConnectionError as e:
print(f"DB connection lost: {e}")
raise RuntimeError("Database operation failed") from e
# The 'from e' preserves the original error (error chaining)
# Better practice: re-raise or wrap appropriately
class DataProcessingError(Exception):
pass
def load_and_process_data(filename):
try:
with open(filename) as f:
raw_data = f.read()
return process_data(raw_data)
except FileNotFoundError as e:
raise DataProcessingError(f"Cannot load {filename}") from e
except ValueError as e:
raise DataProcessingError(f"Invalid data format") from e
# Catches at higher level
try:
result = load_and_process_data("data.json")
except DataProcessingError as e:
print(f"Error: {e}")# 'else' block runs only if NO exception occurred
def divide_numbers(a, b):
try:
result = a / b
except TypeError:
print("Arguments must be numbers")
return None
except ZeroDivisionError:
print("Cannot divide by zero")
return None
else:
# Only runs if no exception
print(f"Division successful: {a} / {b} = {result}")
return result
# Test
divide_numbers(10, 2) # Prints success message
divide_numbers(10, 0) # Prints zero division error
divide_numbers(10, "2") # Prints type error
# More complex example
def process_user_input(user_input):
try:
user_age = int(user_input)
except ValueError:
print("Please enter a valid number")
return False
else:
# Only executes if int() succeeds
if user_age >= 18:
print(f"Welcome! You are {user_age} years old.")
return True
else:
print("You must be 18 or older.")
return False
# Test
process_user_input("25") # Success path
process_user_input("abc") # Error path
process_user_input("15") # Else block with failed condition# 'finally' block ALWAYS runs, even with exceptions
def read_important_file(filename):
file = None
try:
file = open(filename)
return file.read()
except FileNotFoundError:
print(f"File {filename} not found")
return None
finally:
# This ALWAYS runs
if file:
file.close()
print("File closed")
# Better: use context manager instead
def read_important_file(filename):
try:
with open(filename) as file:
return file.read()
except FileNotFoundError:
print(f"File {filename} not found")
return None
# File automatically closes in finally block# Pattern 1: Different handling for different errors
try:
result = operation()
except SpecificError1:
handle_error1()
except SpecificError2:
handle_error2()
# Pattern 2: Same handling for multiple errors
try:
result = operation()
except (Error1, Error2, Error3):
handle_all_errors()
# Pattern 3: Catch specific, then general
try:
result = operation()
except SpecificError:
handle_specific()
except GeneralError:
handle_general()
# Pattern 4: Do something only on success
try:
result = operation()
except Error:
handle_error()
else:
use_result(result)
# Pattern 5: Always cleanup
try:
result = operation()
except Error:
handle_error()
finally:
cleanup()Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward