Error Handling - Graceful Recovery from Mistakes

Errors (exceptions) happen when Python can't execute your code. Instead of crashing, you can 'catch' errors and handle them gracefully. Try-except blocks: try to run code, and if an error occurs, execute the except block. You can have multiple except blocks for different error types.

# Error handling examples
# Example 1: Basic try-except
print("Example 1: Basic error handling")

try:
    number = int(input("Enter a number: "))
    result = 10 / number
    print(f"10 divided by {number} = {result}")
except ValueError:
    print("Error: That's not a valid number!")
except ZeroDivisionError:
    print("Error: Cannot divide by zero!")
except:
    print("Error: Something went wrong!")

# Example 2: Getting error details
print("\nExample 2: Getting error information")

try:
    age = int(input("Enter your age: "))
    print(f"Next year you'll be {age + 1}")
except ValueError as e:
    print(f"ValueError occurred: {e}")
    print(f"Error type: {type(e)}")

# Example 3: Else and Finally
print("\nExample 3: Else and Finally blocks")

def divide_numbers(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        return "Cannot divide by zero!"
    except TypeError:
        return "Please enter numbers only!"
    else:
        # Runs only if no error occurred
        return f"Result: {result}"
    finally:
        # Always runs, error or not
        print("Division operation completed.")

print(divide_numbers(10, 2))
print(divide_numbers(10, 0))
print(divide_numbers(10, "two"))

# Example 4: Multiple operations with error handling
print("\nExample 4: Calculator with error handling")

while True:
    print("\nCalculator Menu:")
    print("1. Add")
    print("2. Subtract")
    print("3. Multiply")
    print("4. Divide")
    print("5. Exit")
    
    choice = input("Enter choice (1-5): ")
    
    if choice == "5":
        print("Goodbye!")
        break
    
    try:
        num1 = float(input("Enter first number: "))
        num2 = float(input("Enter second number: "))
        
        if choice == "1":
            result = num1 + num2
            operation = "+"
        elif choice == "2":
            result = num1 - num2
            operation = "-"
        elif choice == "3":
            result = num1 * num2
            operation = "*"
        elif choice == "4":
            if num2 == 0:
                raise ZeroDivisionError("Cannot divide by zero")
            result = num1 / num2
            operation = "/"
        else:
            print("Invalid choice!")
            continue
            
        print(f"{num1} {operation} {num2} = {result}")
        
    except ValueError:
        print("Error: Please enter valid numbers!")
    except ZeroDivisionError as e:
        print(f"Error: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")

# Example 5: Creating custom errors
print("\nExample 5: Custom error checking")

def check_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative!")
    elif age < 18:
        return "Minor"
    elif age > 120:
        raise ValueError("Age seems unrealistic!")
    else:
        return "Adult"

# Test the function
test_ages = [15, 25, -5, 150]

for age in test_ages:
    try:
        category = check_age(age)
        print(f"Age {age}: {category}")
    except ValueError as e:
        print(f"Age {age}: ERROR - {e}")

# Example 6: File error handling
print("\nExample 6: Safe file operations")

filename = input("\nEnter filename to read: ")

try:
    with open(filename, "r") as file:
        content = file.read()
        print(f"\nFile contents:\n{content}")
except FileNotFoundError:
    print(f"Error: File '{filename}' not found!")
except PermissionError:
    print(f"Error: No permission to read '{filename}'!")
except UnicodeDecodeError:
    print(f"Error: Cannot read '{filename}' as text file!")
except Exception as e:
    print(f"Unexpected error: {type(e).__name__}: {e}")