mirror of
https://github.com/blshaer/python-by-example.git
synced 2026-03-27 23:29:25 +01:00
336 lines
9.9 KiB
Python
336 lines
9.9 KiB
Python
"""
|
|
================================================================================
|
|
File: 01_try_except.py
|
|
Topic: Exception Handling with try-except
|
|
================================================================================
|
|
|
|
This file demonstrates error handling in Python using try-except blocks.
|
|
Proper error handling makes your code more robust and user-friendly by
|
|
gracefully managing unexpected situations.
|
|
|
|
Key Concepts:
|
|
- try, except, else, finally blocks
|
|
- Catching specific exceptions
|
|
- Exception hierarchy
|
|
- Raising exceptions
|
|
- Getting exception information
|
|
|
|
================================================================================
|
|
"""
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 1. Basic try-except
|
|
# -----------------------------------------------------------------------------
|
|
# Catch and handle errors that would otherwise crash your program
|
|
|
|
print("--- Basic try-except ---")
|
|
|
|
# Without error handling - this would crash:
|
|
# result = 10 / 0 # ZeroDivisionError!
|
|
|
|
# With error handling:
|
|
try:
|
|
result = 10 / 0
|
|
except:
|
|
print("An error occurred!")
|
|
|
|
# Better - catch specific exception:
|
|
try:
|
|
result = 10 / 0
|
|
except ZeroDivisionError:
|
|
print("Cannot divide by zero!")
|
|
result = 0
|
|
|
|
print(f"Result: {result}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 2. Catching Specific Exceptions
|
|
# -----------------------------------------------------------------------------
|
|
# Different errors need different handling
|
|
|
|
print("\n--- Catching Specific Exceptions ---")
|
|
|
|
def safe_divide(a, b):
|
|
"""Divide with specific error handling."""
|
|
try:
|
|
result = a / b
|
|
except ZeroDivisionError:
|
|
print(" Error: Cannot divide by zero")
|
|
return None
|
|
except TypeError:
|
|
print(" Error: Invalid types for division")
|
|
return None
|
|
return result
|
|
|
|
print(f"10 / 2 = {safe_divide(10, 2)}")
|
|
print(f"10 / 0 = {safe_divide(10, 0)}")
|
|
print(f"10 / 'a' = {safe_divide(10, 'a')}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 3. Catching Multiple Exceptions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Multiple Exceptions ---")
|
|
|
|
# Catch multiple in one line
|
|
def process_number(value):
|
|
"""Process a number with multiple exception handlers."""
|
|
try:
|
|
# This might raise ValueError or TypeError
|
|
number = int(value)
|
|
result = 100 / number
|
|
return result
|
|
except (ValueError, TypeError) as e:
|
|
print(f" Conversion error: {e}")
|
|
return None
|
|
except ZeroDivisionError:
|
|
print(" Cannot divide by zero")
|
|
return None
|
|
|
|
print(f"process_number('5'): {process_number('5')}")
|
|
print(f"process_number('abc'): {process_number('abc')}")
|
|
print(f"process_number('0'): {process_number('0')}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 4. The else Clause
|
|
# -----------------------------------------------------------------------------
|
|
# Runs only if no exception occurred
|
|
|
|
print("\n--- The else Clause ---")
|
|
|
|
def divide_with_else(a, b):
|
|
"""Division with else clause for success logging."""
|
|
try:
|
|
result = a / b
|
|
except ZeroDivisionError:
|
|
print(" Division failed: Cannot divide by zero")
|
|
return None
|
|
else:
|
|
# Only runs if try block succeeded
|
|
print(f" Division successful: {a} / {b} = {result}")
|
|
return result
|
|
|
|
divide_with_else(10, 2)
|
|
divide_with_else(10, 0)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 5. The finally Clause
|
|
# -----------------------------------------------------------------------------
|
|
# Always runs, regardless of whether an exception occurred
|
|
|
|
print("\n--- The finally Clause ---")
|
|
|
|
def read_file_example(filename):
|
|
"""Demonstrate finally for cleanup."""
|
|
file = None
|
|
try:
|
|
print(f" Attempting to open '{filename}'...")
|
|
# Simulating file operation
|
|
if filename == "missing.txt":
|
|
raise FileNotFoundError("File not found")
|
|
print(" File opened successfully!")
|
|
return "File content"
|
|
except FileNotFoundError as e:
|
|
print(f" Error: {e}")
|
|
return None
|
|
finally:
|
|
# This ALWAYS runs
|
|
print(" Cleanup: Closing file (if open)")
|
|
|
|
read_file_example("data.txt")
|
|
print()
|
|
read_file_example("missing.txt")
|
|
|
|
# Real-world pattern with file
|
|
print("\n--- Real file handling ---")
|
|
|
|
# Best practice: use 'with' statement (handles cleanup automatically)
|
|
# But for learning, here's the try-finally pattern:
|
|
"""
|
|
file = None
|
|
try:
|
|
file = open("data.txt", "r")
|
|
content = file.read()
|
|
finally:
|
|
if file:
|
|
file.close() # Always closes, even if error occurred
|
|
"""
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 6. Complete try-except-else-finally
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Complete Pattern ---")
|
|
|
|
def complete_example(value):
|
|
"""Show all four clauses in action."""
|
|
print(f"\n Processing: {value}")
|
|
try:
|
|
number = int(value)
|
|
result = 100 / number
|
|
except ValueError:
|
|
print(" EXCEPT: Not a valid integer")
|
|
result = None
|
|
except ZeroDivisionError:
|
|
print(" EXCEPT: Division by zero")
|
|
result = None
|
|
else:
|
|
print(f" ELSE: Success! Result = {result}")
|
|
finally:
|
|
print(" FINALLY: This always runs")
|
|
|
|
return result
|
|
|
|
complete_example("10")
|
|
complete_example("abc")
|
|
complete_example("0")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 7. Getting Exception Information
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Exception Information ---")
|
|
|
|
try:
|
|
numbers = [1, 2, 3]
|
|
print(numbers[10])
|
|
except IndexError as e:
|
|
print(f"Exception type: {type(e).__name__}")
|
|
print(f"Exception message: {e}")
|
|
print(f"Exception args: {e.args}")
|
|
|
|
# Getting full traceback
|
|
import traceback
|
|
|
|
try:
|
|
result = 1 / 0
|
|
except ZeroDivisionError:
|
|
print("\nFull traceback:")
|
|
traceback.print_exc()
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 8. Common Built-in Exceptions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Common Exceptions ---")
|
|
|
|
exceptions_demo = [
|
|
("ValueError", "int('abc')"),
|
|
("TypeError", "'2' + 2"),
|
|
("IndexError", "[1,2,3][10]"),
|
|
("KeyError", "{}['missing']"),
|
|
("AttributeError", "'string'.missing_method()"),
|
|
("FileNotFoundError", "open('nonexistent.txt')"),
|
|
("ZeroDivisionError", "1/0"),
|
|
("ImportError", "import nonexistent_module"),
|
|
("NameError", "undefined_variable"),
|
|
]
|
|
|
|
print("Common exception types:")
|
|
for name, example in exceptions_demo:
|
|
print(f" {name}: {example}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 9. Exception Hierarchy
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Exception Hierarchy ---")
|
|
|
|
# All exceptions inherit from BaseException
|
|
# Most use Exception as base
|
|
|
|
hierarchy = """
|
|
BaseException
|
|
├── SystemExit
|
|
├── KeyboardInterrupt
|
|
├── GeneratorExit
|
|
└── Exception
|
|
├── StopIteration
|
|
├── ArithmeticError
|
|
│ ├── ZeroDivisionError
|
|
│ ├── FloatingPointError
|
|
│ └── OverflowError
|
|
├── LookupError
|
|
│ ├── IndexError
|
|
│ └── KeyError
|
|
├── ValueError
|
|
├── TypeError
|
|
├── AttributeError
|
|
├── OSError
|
|
│ ├── FileNotFoundError
|
|
│ └── PermissionError
|
|
└── RuntimeError
|
|
"""
|
|
|
|
print("Exception hierarchy (simplified):")
|
|
print(hierarchy)
|
|
|
|
# Catching parent catches all children
|
|
try:
|
|
result = 1 / 0
|
|
except ArithmeticError: # Catches ZeroDivisionError too!
|
|
print("Caught an arithmetic error (parent class)")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 10. Practical Examples
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Practical Examples ---")
|
|
|
|
# Example 1: User input validation
|
|
def get_positive_number(prompt):
|
|
"""Get a positive number from user (simulated)."""
|
|
test_inputs = ["abc", "-5", "0", "10"]
|
|
|
|
for user_input in test_inputs:
|
|
print(f" Input: '{user_input}'", end=" → ")
|
|
try:
|
|
number = float(user_input)
|
|
if number <= 0:
|
|
raise ValueError("Number must be positive")
|
|
print(f"Valid! ({number})")
|
|
return number
|
|
except ValueError as e:
|
|
print(f"Invalid: {e}")
|
|
return None
|
|
|
|
print("User input validation:")
|
|
get_positive_number("Enter a positive number: ")
|
|
|
|
# Example 2: Safe dictionary access
|
|
def safe_get(dictionary, *keys, default=None):
|
|
"""Safely navigate nested dictionary."""
|
|
current = dictionary
|
|
try:
|
|
for key in keys:
|
|
current = current[key]
|
|
return current
|
|
except (KeyError, TypeError):
|
|
return default
|
|
|
|
data = {"user": {"profile": {"name": "Alice"}}}
|
|
print(f"\nNested access: {safe_get(data, 'user', 'profile', 'name')}")
|
|
print(f"Missing key: {safe_get(data, 'user', 'missing', 'name', default='N/A')}")
|
|
|
|
# Example 3: Retry pattern
|
|
def fetch_with_retry(max_retries=3):
|
|
"""Simulate fetching with retry on failure."""
|
|
import random
|
|
|
|
for attempt in range(1, max_retries + 1):
|
|
try:
|
|
print(f" Attempt {attempt}...", end=" ")
|
|
# Simulate random failure
|
|
if random.random() < 0.7: # 70% chance of failure
|
|
raise ConnectionError("Network error")
|
|
print("Success!")
|
|
return "Data"
|
|
except ConnectionError as e:
|
|
print(f"Failed: {e}")
|
|
if attempt == max_retries:
|
|
print(" All retries exhausted!")
|
|
return None
|
|
|
|
print("\nRetry pattern:")
|
|
fetch_with_retry()
|