Skip to content

Exception Handling ​

🚨 What are Exceptions? ​

Exceptions are errors that occur during program execution. Instead of crashing, Python allows you to handle these errors gracefully.

Common Exception Types ​

python
# ZeroDivisionError
result = 10 / 0

# ValueError
number = int("not_a_number")

# TypeError
result = "hello" + 5

# IndexError
my_list = [1, 2, 3]
item = my_list[10]

# KeyError
my_dict = {"name": "Alice"}
age = my_dict["age"]

# FileNotFoundError
with open("nonexistent_file.txt", "r") as file:
    content = file.read()

πŸ›‘οΈ Basic Exception Handling ​

try-except Block ​

python
try:
    # Code that might cause an exception
    result = 10 / 0
except ZeroDivisionError:
    # Handle the specific exception
    print("Cannot divide by zero!")

Handling Multiple Exceptions ​

python
try:
    number = int(input("Enter a number: "))
    result = 10 / number
    print(f"Result: {result}")
except ValueError:
    print("Invalid input! Please enter a number.")
except ZeroDivisionError:
    print("Cannot divide by zero!")

Handling Multiple Exceptions in One Block ​

python
try:
    number = int(input("Enter a number: "))
    result = 10 / number
    print(f"Result: {result}")
except (ValueError, ZeroDivisionError) as e:
    print(f"Error occurred: {e}")

πŸ”§ Advanced Exception Handling ​

try-except-else-finally ​

python
try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("File not found!")
except PermissionError:
    print("Permission denied!")
else:
    # Runs if no exception occurs
    print("File read successfully!")
    print(content)
finally:
    # Always runs, regardless of exceptions
    try:
        file.close()
        print("File closed.")
    except:
        pass

Generic Exception Handling ​

python
try:
    # Some risky operation
    result = risky_operation()
except Exception as e:
    # Catches any exception
    print(f"An error occurred: {e}")
    print(f"Error type: {type(e).__name__}")

🎯 Getting Exception Information ​

python
import traceback

try:
    result = 10 / 0
except Exception as e:
    print(f"Exception message: {e}")
    print(f"Exception type: {type(e).__name__}")
    
    # Print full traceback
    traceback.print_exc()
    
    # Get traceback as string
    tb_str = traceback.format_exc()
    print(tb_str)

πŸš€ Custom Exceptions ​

Creating Custom Exceptions ​

python
class CustomError(Exception):
    """Base class for custom exceptions"""
    pass

class ValidationError(CustomError):
    """Raised when validation fails"""
    pass

class InsufficientFundsError(CustomError):
    """Raised when account has insufficient funds"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"Insufficient funds: balance={balance}, required={amount}")

Raising Custom Exceptions ​

python
def validate_age(age):
    if age < 0:
        raise ValidationError("Age cannot be negative")
    if age > 150:
        raise ValidationError("Age cannot exceed 150")
    return age

def withdraw_money(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

# Usage
try:
    age = validate_age(-5)
except ValidationError as e:
    print(f"Validation error: {e}")

try:
    new_balance = withdraw_money(100, 150)
except InsufficientFundsError as e:
    print(f"Transaction failed: {e}")

πŸ”„ Exception Propagation ​

python
def level_3():
    raise ValueError("Error from level 3")

def level_2():
    level_3()  # Exception propagates up

def level_1():
    try:
        level_2()
    except ValueError as e:
        print(f"Caught in level_1: {e}")

level_1()  # Handles the exception

πŸš€ Practice Examples ​

Example 1: Safe Calculator ​

python
def safe_calculator():
    while True:
        try:
            num1 = float(input("Enter first number: "))
            operator = input("Enter operator (+, -, *, /): ")
            num2 = float(input("Enter second number: "))
            
            if operator == '+':
                result = num1 + num2
            elif operator == '-':
                result = num1 - num2
            elif operator == '*':
                result = num1 * num2
            elif operator == '/':
                if num2 == 0:
                    raise ZeroDivisionError("Cannot divide by zero")
                result = num1 / num2
            else:
                raise ValueError("Invalid operator")
            
            print(f"Result: {result}")
            break
            
        except ValueError as e:
            print(f"Invalid input: {e}")
        except ZeroDivisionError as e:
            print(f"Math error: {e}")
        except KeyboardInterrupt:
            print("\nCalculator stopped by user")
            break
        except Exception as e:
            print(f"Unexpected error: {e}")

safe_calculator()

Example 2: File Processor ​

python
def process_file(filename):
    try:
        with open(filename, 'r') as file:
            lines = file.readlines()
            
        processed_lines = []
        for i, line in enumerate(lines, 1):
            try:
                # Try to convert each line to a number
                number = float(line.strip())
                processed_lines.append(number * 2)
            except ValueError:
                print(f"Warning: Line {i} is not a number, skipping")
                continue
        
        return processed_lines
        
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found")
        return []
    except PermissionError:
        print(f"Error: Permission denied to read '{filename}'")
        return []
    except Exception as e:
        print(f"Unexpected error processing file: {e}")
        return []

# Usage
numbers = process_file('numbers.txt')
if numbers:
    print(f"Processed {len(numbers)} numbers")
    print(f"Average: {sum(numbers) / len(numbers):.2f}")

Example 3: Network Request Handler ​

python
import time
import random

class NetworkError(Exception):
    pass

class TimeoutError(NetworkError):
    pass

class ConnectionError(NetworkError):
    pass

def simulate_network_request(url, retries=3):
    for attempt in range(retries):
        try:
            print(f"Attempt {attempt + 1} to connect to {url}")
            
            # Simulate network conditions
            if random.random() < 0.3:  # 30% chance of timeout
                raise TimeoutError("Request timed out")
            if random.random() < 0.2:  # 20% chance of connection error
                raise ConnectionError("Connection failed")
            
            # Simulate successful response
            return {"status": "success", "data": "Sample data"}
            
        except TimeoutError:
            print(f"Timeout on attempt {attempt + 1}")
            if attempt == retries - 1:
                raise
            time.sleep(1)  # Wait before retry
            
        except ConnectionError:
            print(f"Connection failed on attempt {attempt + 1}")
            if attempt == retries - 1:
                raise
            time.sleep(2)  # Wait longer for connection issues

# Usage
try:
    response = simulate_network_request("https://api.example.com/data")
    print(f"Success: {response}")
except NetworkError as e:
    print(f"Network request failed: {e}")

Example 4: Data Validator ​

python
class ValidationError(Exception):
    pass

class EmailValidationError(ValidationError):
    pass

class PasswordValidationError(ValidationError):
    pass

def validate_email(email):
    if '@' not in email:
        raise EmailValidationError("Email must contain @ symbol")
    if '.' not in email.split('@')[1]:
        raise EmailValidationError("Email must have valid domain")
    return email

def validate_password(password):
    if len(password) < 8:
        raise PasswordValidationError("Password must be at least 8 characters")
    if not any(c.isupper() for c in password):
        raise PasswordValidationError("Password must contain uppercase letter")
    if not any(c.islower() for c in password):
        raise PasswordValidationError("Password must contain lowercase letter")
    if not any(c.isdigit() for c in password):
        raise PasswordValidationError("Password must contain a digit")
    return password

def register_user(email, password):
    try:
        valid_email = validate_email(email)
        valid_password = validate_password(password)
        
        # Simulate user registration
        print(f"User registered successfully with email: {valid_email}")
        return True
        
    except EmailValidationError as e:
        print(f"Email validation failed: {e}")
        return False
    except PasswordValidationError as e:
        print(f"Password validation failed: {e}")
        return False
    except Exception as e:
        print(f"Registration failed: {e}")
        return False

# Usage
success = register_user("user@example.com", "MyPassword123")
if success:
    print("Registration completed!")

🎯 Best Practices ​

1. Be Specific with Exceptions ​

python
# Bad - too generic
try:
    process_data()
except:
    print("Something went wrong")

# Good - specific handling
try:
    process_data()
except ValueError:
    print("Invalid data format")
except FileNotFoundError:
    print("Data file not found")
except Exception as e:
    print(f"Unexpected error: {e}")

2. Use finally for Cleanup ​

python
resource = None
try:
    resource = acquire_resource()
    process_resource(resource)
except Exception as e:
    print(f"Error: {e}")
finally:
    if resource:
        release_resource(resource)

3. Log Exceptions ​

python
import logging

logging.basicConfig(level=logging.ERROR)

try:
    risky_operation()
except Exception as e:
    logging.error(f"Operation failed: {e}", exc_info=True)

🎯 Key Takeaways ​

  • Exceptions allow graceful error handling
  • Use try-except blocks to catch and handle errors
  • finally blocks always execute, good for cleanup
  • else blocks run only if no exception occurs
  • Create custom exceptions for specific error cases
  • Be specific when catching exceptions
  • Use logging to track exceptions in production
  • Don't ignore exceptions - handle them appropriately

Continue to: Object-Oriented Programming β†’

Released under the MIT License.