Ojasa Mirai

Ojasa Mirai

Python

Loading...

Learning Level

🟢 Beginner🔵 Advanced
Exceptions OverviewException TypesTry-Except BlocksRaising ExceptionsCustom ExceptionsMultiple ExceptionsFinally & CleanupDebugging TechniquesLogging Best Practices
Python/Error Handling/Multiple Exceptions

🔀 Multiple Exceptions — Handling Complex Error Scenarios

Learn patterns for handling multiple different exceptions that can occur in a single operation.


📌 Multiple Except Blocks

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 handling

🎯 Catching Multiple Exceptions at Once

When 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 user

🏗️ Exception Order Matters

Catch 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}")

📊 Real-World Pattern: Data Processing

# 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")

🎨 Using Parent Classes for Exception Groups

# 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

🔄 Error Chain: Preserving Context

# 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}")

📋 Pattern: Try-Except-Else

# '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

🎯 Pattern: Try-Except-Finally

# '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

💡 Common Patterns Summary

# 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()

🎯 Key Takeaways

  • ✅ Use multiple except blocks for different errors
  • ✅ Catch specific exceptions before general ones
  • ✅ Use tuple syntax for exceptions with same handling
  • ✅ Use parent exceptions to catch groups of related errors
  • ✅ `else` block runs only if no exception occurs
  • ✅ `finally` block always runs for cleanup
  • ✅ Order matters: specific before general


Resources

Python Docs

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

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn