Writing Robust Code that Handles Errors Gracefully ๐ก๏ธโจ
Exceptions are errors that occur during program execution.
Without proper handling, they crash your program! ๐ฅ
ValueError: invalid literal for int() with base 10: 'hello'
Letโs see a real example!
ZeroDivisionError type errors result when a value is being divided by zeroif-else) to check if a divisor is a zero to prevent a crashOutput:
Cannot divide by zero!
The program continues running! โ
Why is this better?
| Exception | When It Happens |
|---|---|
ValueError |
Invalid value (e.g., int("hello")) |
ZeroDivisionError |
Division by zero |
TypeError |
Wrong type (e.g., "5" + 5) |
FileNotFoundError |
File doesnโt exist |
IndexError |
List index out of range |
KeyError |
Dictionary key doesnโt exist |
This code crashes. Add exception handling to make it safe:
Hints:
as Keyword ๐Example output:
Enter your age: twenty
Invalid input: invalid literal for int() with base 10: 'twenty'
else Clause โจRuns only if no exception occurs:
finally Clause ๐Runs no matter what - even if thereโs an exception:
Perfect for cleanup operations! ๐งน
Create a function that safely reads a file:
Requirements:
FileNotFoundErrorfinally)None if file doesnโt existWhen the same action handles multiple errors:
def safe_calculator():
try:
num1 = float(input("First number: "))
operator = input("Operator (+, -, *, /): ")
num2 = float(input("Second number: "))
if operator == "+":
result = num1 + num2
elif operator == "/":
result = num1 / num2
# ... more operators
print(f"Result: {result}")
except ValueError:
print("Please enter valid numbers!")
except ZeroDivisionError:
print("Cannot divide by zero!")Fix this code to handle invalid indices:
Bonus: Also handle if index is not a number!
def get_item(my_list, index):
try:
item = my_list[index]
return item
except IndexError:
print(f"Index {index} is out of range!")
return None
except TypeError:
print("Index must be an integer!")
return None
# Test it:
fruits = ["apple", "banana", "cherry"]
print(get_item(fruits, 5)) # Index error
print(get_item(fruits, "2")) # Type errorSo far weโve been catching exceptions. But you can also raise them!
Why raise exceptions?
raise Statement ๐ขBasic syntax:
Common built-in exceptions to raise:
ValueError - Invalid valueTypeError - Wrong typeRuntimeError - General runtime errorNotImplementedError - Feature not implemented yetOutput: Error: Age cannot be negative!
Principle: Raise exceptions when you canโt fix the problem, but the caller might be able to!
def safe_divide(numerator, denominator):
"""Divide two numbers with validation."""
if not isinstance(numerator, (int, float)):
raise TypeError("Numerator must be a number")
if not isinstance(denominator, (int, float)):
raise TypeError("Denominator must be a number")
if denominator == 0:
raise ValueError("Cannot divide by zero")
return numerator / denominatorOutput:
Result: 5.0
Value error: Cannot divide by zero
Create your own exception types:
Create a function that validates an email address:
def validate_email(email):
if not email:
raise ValueError("Email cannot be empty!")
if "@" not in email:
raise ValueError("Email must contain @")
if "." not in email.split("@")[1]:
raise ValueError("Email must have domain extension")
return True
try:
validate_email("test@example.com")
print("โ
Valid email!")
except ValueError as error:
print(f"โ {error}")import requests
def fetch_data(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
except requests.Timeout:
print("Request timed out!")
except requests.ConnectionError:
print("Network connection failed!")
except requests.HTTPError as e:
print(f"HTTP error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
finally:
print("Request completed")finally for files, connectionsCreate a BankAccount class with exception handling:
class InsufficientFundsError(Exception):
pass
class BankAccount:
def __init__(self, balance=0):
# Initialize account
pass
def deposit(self, amount):
# Add exception handling for negative amounts
pass
def withdraw(self, amount):
# Raise InsufficientFundsError if balance too low
# Raise ValueError if amount is negative
passclass InsufficientFundsError(Exception):
pass
class BankAccount:
def __init__(self, balance=0):
if balance < 0:
raise ValueError("Initial balance cannot be negative")
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self.balance:
raise InsufficientFundsError(
f"Insufficient funds. Balance: ${self.balance}"
)
self.balance -= amount
return self.balancetry:
account = BankAccount(100)
print(f"Balance: ${account.balance}")
account.deposit(50)
print(f"After deposit: ${account.balance}")
account.withdraw(200) # This will fail!
except ValueError as e:
print(f"Invalid operation: {e}")
except InsufficientFundsError as e:
print(f"Transaction failed: {e}")| Clause | Purpose | When It Runs |
|---|---|---|
try: |
Code that might fail | Always attempted |
except: |
Error handling | Only if exception occurs |
else: |
Success code | Only if NO exception |
finally: |
Cleanup code | ALWAYS runs |
try-except to handle errors gracefullyfinally