python
examples
examples.py🐍python
"""
08 - Error Handling: Examples
Run this file to see exception handling in action!
"""
print("=" * 60)
print("ERROR HANDLING - EXAMPLES")
print("=" * 60)
# =============================================================================
# 1. BASIC TRY-EXCEPT
# =============================================================================
print("\n--- 1. Basic Try-Except ---\n")
# Without error handling - would crash!
# result = 10 / 0 # ZeroDivisionError!
# With error handling
try:
result = 10 / 0
except ZeroDivisionError:
print("Caught ZeroDivisionError: Cannot divide by zero!")
# Catching any exception
try:
numbers = [1, 2, 3]
print(numbers[10])
except Exception as e:
print(f"Caught exception: {type(e).__name__}: {e}")
# =============================================================================
# 2. MULTIPLE EXCEPTION TYPES
# =============================================================================
print("\n--- 2. Multiple Exception Types ---\n")
def risky_operation(value):
"""A function that might raise different exceptions."""
try:
# Could raise TypeError, ZeroDivisionError, or ValueError
if value == "text":
return int("not a number")
elif value == 0:
return 10 / value
elif value < 0:
raise ValueError("Negative values not allowed")
else:
return 10 / value
except ZeroDivisionError:
print(" Error: Division by zero!")
return None
except ValueError as e:
print(f" Error: Invalid value - {e}")
return None
except TypeError:
print(" Error: Wrong type!")
return None
print(f"risky_operation(2) = {risky_operation(2)}")
print(f"risky_operation(0) = {risky_operation(0)}")
print(f"risky_operation(-5) = {risky_operation(-5)}")
print(f"risky_operation('text') = {risky_operation('text')}")
# Catching multiple exceptions in one except block
try:
# Some operation
x = int("not a number")
except (ValueError, TypeError) as e:
print(f"\nCaught ValueError or TypeError: {e}")
# =============================================================================
# 3. ELSE AND FINALLY CLAUSES
# =============================================================================
print("\n--- 3. Else and Finally Clauses ---\n")
def divide(a, b):
"""Demonstrate try-except-else-finally."""
try:
result = a / b
except ZeroDivisionError:
print(f" Cannot divide {a} by {b}")
return None
else:
# Only runs if NO exception was raised
print(f" Division successful!")
return result
finally:
# ALWAYS runs, exception or not
print(f" Attempted to divide {a} by {b}")
print("Testing divide(10, 2):")
print(f" Result: {divide(10, 2)}")
print("\nTesting divide(10, 0):")
print(f" Result: {divide(10, 0)}")
# =============================================================================
# 4. RAISING EXCEPTIONS
# =============================================================================
print("\n--- 4. Raising Exceptions ---\n")
def validate_age(age):
"""Validate that age is a reasonable value."""
if not isinstance(age, int):
raise TypeError(f"Age must be an integer, got {type(age).__name__}")
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age is unrealistically high")
return True
# Test validation
test_ages = [25, -5, 200, "thirty"]
for age in test_ages:
try:
validate_age(age)
print(f" Age {age}: Valid")
except (TypeError, ValueError) as e:
print(f" Age {age}: Invalid - {e}")
# =============================================================================
# 5. RE-RAISING EXCEPTIONS
# =============================================================================
print("\n--- 5. Re-raising Exceptions ---\n")
def process_data(data):
"""Process data, log errors, and re-raise."""
try:
# Simulate processing
result = int(data)
return result * 2
except ValueError:
print(f" [LOG] Failed to process: {data}")
raise # Re-raise the same exception
try:
process_data("not_a_number")
except ValueError as e:
print(f" Caller caught re-raised exception: {e}")
# =============================================================================
# 6. CUSTOM EXCEPTIONS
# =============================================================================
print("\n--- 6. Custom Exceptions ---\n")
# Define custom exception classes
class ValidationError(Exception):
"""Exception for validation errors."""
pass
class InsufficientFundsError(Exception):
"""Exception when account has insufficient funds."""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"Cannot withdraw ${amount:.2f}: only ${balance:.2f} available"
)
class BankAccount:
"""Simple bank account with custom exceptions."""
def __init__(self, balance=0):
if balance < 0:
raise ValidationError("Initial balance cannot be negative")
self.balance = balance
def withdraw(self, amount):
if amount <= 0:
raise ValidationError("Withdrawal amount must be positive")
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
# Test custom exceptions
try:
account = BankAccount(100)
print(f" Created account with balance: ${account.balance:.2f}")
account.withdraw(30)
print(f" After withdrawing $30: ${account.balance:.2f}")
account.withdraw(100) # This will fail
except InsufficientFundsError as e:
print(f" Error: {e}")
print(f" Details: Balance=${e.balance:.2f}, Attempted=${e.amount:.2f}")
# =============================================================================
# 7. EXCEPTION CHAINING
# =============================================================================
print("\n--- 7. Exception Chaining ---\n")
def fetch_user(user_id):
"""Simulate fetching user that might fail."""
if user_id < 0:
raise ValueError(f"Invalid user ID: {user_id}")
return {"id": user_id, "name": "Alice"}
def process_user_request(user_id):
"""Process request, wrapping low-level errors."""
try:
user = fetch_user(user_id)
return f"Processed request for {user['name']}"
except ValueError as e:
# Chain with 'from' to preserve original exception
raise RuntimeError("Failed to process user request") from e
try:
process_user_request(-1)
except RuntimeError as e:
print(f" Caught: {e}")
print(f" Caused by: {e.__cause__}")
# =============================================================================
# 8. EXCEPTION HIERARCHY
# =============================================================================
print("\n--- 8. Exception Hierarchy ---\n")
# All exceptions inherit from BaseException
# Most inherit from Exception
# More specific exceptions inherit from general ones
print("Exception hierarchy (partial):")
print(" BaseException")
print(" ├── SystemExit")
print(" ├── KeyboardInterrupt")
print(" └── Exception")
print(" ├── ArithmeticError")
print(" │ ├── ZeroDivisionError")
print(" │ └── OverflowError")
print(" ├── LookupError")
print(" │ ├── IndexError")
print(" │ └── KeyError")
print(" ├── ValueError")
print(" ├── TypeError")
print(" └── OSError")
print(" ├── FileNotFoundError")
print(" └── PermissionError")
# Catch parent to catch children
try:
numbers = [1, 2, 3]
print(numbers[10])
except LookupError: # Catches IndexError (and KeyError)
print("\n Caught LookupError (covers IndexError and KeyError)")
# =============================================================================
# 9. CONTEXT MANAGERS FOR CLEANUP
# =============================================================================
print("\n--- 9. Context Managers for Cleanup ---\n")
# 'with' statement ensures cleanup even on exceptions
class ManagedResource:
"""A resource that needs cleanup."""
def __init__(self, name):
self.name = name
print(f" Acquiring resource: {name}")
def __enter__(self):
print(f" Entering context for: {self.name}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f" Releasing resource: {self.name}")
if exc_type:
print(f" Exception occurred: {exc_type.__name__}: {exc_val}")
# Return True to suppress exception, False to propagate
return False
def do_work(self, fail=False):
if fail:
raise RuntimeError("Work failed!")
print(f" Working with: {self.name}")
# Successful operation
print("Successful operation:")
with ManagedResource("Database") as resource:
resource.do_work()
# Operation with exception
print("\nOperation with exception:")
try:
with ManagedResource("Database") as resource:
resource.do_work(fail=True)
except RuntimeError:
print(" Exception was propagated to caller")
# =============================================================================
# 10. ASSERTION ERRORS
# =============================================================================
print("\n--- 10. Assertions ---\n")
def calculate_average(numbers):
"""Calculate average with assertion for validation."""
assert len(numbers) > 0, "Cannot calculate average of empty list"
assert all(isinstance(n, (int, float)) for n in numbers), "All items must be numbers"
return sum(numbers) / len(numbers)
# Valid case
result = calculate_average([1, 2, 3, 4, 5])
print(f" Average of [1,2,3,4,5]: {result}")
# Invalid case
try:
calculate_average([])
except AssertionError as e:
print(f" Assertion failed: {e}")
# Note: Assertions can be disabled with python -O
print("\n Note: Use assertions for debugging, not for input validation")
# =============================================================================
# 11. EXCEPTION IN LOOPS
# =============================================================================
print("\n--- 11. Exceptions in Loops ---\n")
data = ["1", "2", "three", "4", "five", "6"]
# Continue on error
print("Processing with continue:")
results = []
for item in data:
try:
results.append(int(item))
except ValueError:
print(f" Skipping invalid item: {item}")
continue
print(f" Valid results: {results}")
# Collect all errors
print("\nCollecting all errors:")
results = []
errors = []
for i, item in enumerate(data):
try:
results.append(int(item))
except ValueError as e:
errors.append(f"Index {i}: {item} - {e}")
print(f" Results: {results}")
print(f" Errors: {errors}")
# =============================================================================
# 12. PRACTICAL PATTERNS
# =============================================================================
print("\n--- 12. Practical Patterns ---\n")
# Pattern 1: Default on error
def safe_int(value, default=0):
"""Convert to int with default on error."""
try:
return int(value)
except (ValueError, TypeError):
return default
print(f"safe_int('42') = {safe_int('42')}")
print(f"safe_int('abc') = {safe_int('abc')}")
print(f"safe_int('abc', -1) = {safe_int('abc', -1)}")
# Pattern 2: Retry with backoff
import time
def retry_operation(func, max_retries=3, delay=0.1):
"""Retry a function on failure."""
for attempt in range(max_retries):
try:
return func()
except Exception as e:
print(f" Attempt {attempt + 1} failed: {e}")
if attempt < max_retries - 1:
time.sleep(delay)
raise Exception(f"All {max_retries} attempts failed")
# Simulate flaky operation
attempt_count = 0
def flaky_operation():
global attempt_count
attempt_count += 1
if attempt_count < 3:
raise ConnectionError("Network error")
return "Success!"
print("\nRetry pattern:")
result = retry_operation(flaky_operation)
print(f" Final result: {result}")
# Pattern 3: Exception as control flow (EAFP - Easier to Ask Forgiveness)
print("\nEAFP vs LBYL:")
# LBYL (Look Before You Leap) - Check first
def get_value_lbyl(data, key):
if key in data:
return data[key]
return None
# EAFP (Easier to Ask Forgiveness than Permission) - Try first
def get_value_eafp(data, key):
try:
return data[key]
except KeyError:
return None
data = {"a": 1, "b": 2}
print(f" LBYL: {get_value_lbyl(data, 'c')}")
print(f" EAFP: {get_value_eafp(data, 'c')}")
print(" Python prefers EAFP style")
# =============================================================================
# 13. LOGGING EXCEPTIONS
# =============================================================================
print("\n--- 13. Logging Exceptions ---\n")
import logging
# Configure basic logging
logging.basicConfig(
level=logging.DEBUG,
format=' %(levelname)s: %(message)s'
)
logger = logging.getLogger(__name__)
def risky_function():
"""Function that might fail."""
try:
result = 1 / 0
except ZeroDivisionError:
# Log with exception info
logger.exception("An error occurred in risky_function")
return None
result = risky_function()
# =============================================================================
# 14. WARNINGS
# =============================================================================
print("\n--- 14. Warnings ---\n")
import warnings
def deprecated_function():
"""A function that will be removed in future versions."""
warnings.warn(
"deprecated_function is deprecated, use new_function instead",
DeprecationWarning,
stacklevel=2
)
return "Old result"
# Capture warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
result = deprecated_function()
if w:
print(f" Warning: {w[0].message}")
print(f" Category: {w[0].category.__name__}")
print("\n" + "=" * 60)
print("END OF EXAMPLES")
print("=" * 60)