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:
passGeneric 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 β