509 lines
16 KiB
Text
509 lines
16 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Python Error Handling - try, except, and Beyond\n",
|
|
"\n",
|
|
"This notebook covers Python's error handling mechanisms from basic try/except blocks to advanced exception handling patterns.\n",
|
|
"\n",
|
|
"## Learning Objectives\n",
|
|
"- Understand Python's exception hierarchy\n",
|
|
"- Master try, except, else, and finally blocks\n",
|
|
"- Learn to handle multiple exception types\n",
|
|
"- Create and use custom exceptions\n",
|
|
"- Follow error handling best practices"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 1. Understanding Exceptions\n",
|
|
"\n",
|
|
"An **exception** is an event that occurs during program execution that disrupts the normal flow of instructions."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Common exceptions without handling\n",
|
|
"print(\"=== Common Exception Types ===\")\n",
|
|
"\n",
|
|
"def show_exceptions():\n",
|
|
" examples = [\n",
|
|
" (\"ZeroDivisionError\", lambda: 10 / 0),\n",
|
|
" (\"IndexError\", lambda: [1, 2, 3][5]),\n",
|
|
" (\"KeyError\", lambda: {'a': 1}['b']),\n",
|
|
" (\"TypeError\", lambda: \"hello\" + 5),\n",
|
|
" (\"ValueError\", lambda: int(\"not_a_number\"))\n",
|
|
" ]\n",
|
|
" \n",
|
|
" for name, func in examples:\n",
|
|
" try:\n",
|
|
" func()\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ {name}: {e}\")\n",
|
|
"\n",
|
|
"show_exceptions()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Basic try/except Syntax"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Basic try/except structure\n",
|
|
"def safe_divide(a, b):\n",
|
|
" try:\n",
|
|
" result = a / b\n",
|
|
" print(f\"✅ {a} ÷ {b} = {result}\")\n",
|
|
" return result\n",
|
|
" except ZeroDivisionError:\n",
|
|
" print(f\"❌ Cannot divide {a} by zero!\")\n",
|
|
" return None\n",
|
|
"\n",
|
|
"# Test the function\n",
|
|
"safe_divide(10, 2)\n",
|
|
"safe_divide(10, 0)\n",
|
|
"\n",
|
|
"print()\n",
|
|
"\n",
|
|
"# Capturing exception details\n",
|
|
"def analyze_exception(func):\n",
|
|
" try:\n",
|
|
" result = func()\n",
|
|
" print(f\"✅ Success: {result}\")\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ {type(e).__name__}: {e}\")\n",
|
|
"\n",
|
|
"analyze_exception(lambda: 10 / 2) # Success\n",
|
|
"analyze_exception(lambda: 10 / 0) # ZeroDivisionError\n",
|
|
"analyze_exception(lambda: int(\"abc\")) # ValueError"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 3. Multiple Exception Types"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Handling different exceptions separately\n",
|
|
"def robust_calculator(expression):\n",
|
|
" try:\n",
|
|
" result = eval(expression) # Note: eval is dangerous in real applications!\n",
|
|
" print(f\"✅ {expression} = {result}\")\n",
|
|
" return result\n",
|
|
" \n",
|
|
" except ZeroDivisionError:\n",
|
|
" print(f\"❌ Division by zero in: {expression}\")\n",
|
|
" \n",
|
|
" except NameError as e:\n",
|
|
" print(f\"❌ Undefined variable in: {expression}\")\n",
|
|
" \n",
|
|
" except (TypeError, ValueError) as e:\n",
|
|
" print(f\"❌ Type/Value error in: {expression} - {e}\")\n",
|
|
"\n",
|
|
"# Test with various expressions\n",
|
|
"test_expressions = [\"10 + 5\", \"20 / 0\", \"x + 5\", \"'hello' + 5\"]\n",
|
|
"\n",
|
|
"for expr in test_expressions:\n",
|
|
" robust_calculator(expr)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 4. The Complete try/except/else/finally Structure"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Complete structure demonstration\n",
|
|
"def file_processor(filename):\n",
|
|
" file_handle = None\n",
|
|
" \n",
|
|
" try:\n",
|
|
" print(f\"📂 Opening: {filename}\")\n",
|
|
" file_handle = open(filename, 'r')\n",
|
|
" content = file_handle.read()\n",
|
|
" print(f\"📖 Read {len(content)} characters\")\n",
|
|
" \n",
|
|
" except FileNotFoundError:\n",
|
|
" print(f\"❌ File not found: {filename}\")\n",
|
|
" return None\n",
|
|
" \n",
|
|
" except PermissionError:\n",
|
|
" print(f\"❌ Permission denied: {filename}\")\n",
|
|
" return None\n",
|
|
" \n",
|
|
" else:\n",
|
|
" # Runs only if no exception occurred\n",
|
|
" print(\"✅ File processing successful\")\n",
|
|
" return content\n",
|
|
" \n",
|
|
" finally:\n",
|
|
" # Always runs for cleanup\n",
|
|
" if file_handle and not file_handle.closed:\n",
|
|
" print(\"🔒 Closing file\")\n",
|
|
" file_handle.close()\n",
|
|
"\n",
|
|
"# Create a test file\n",
|
|
"with open(\"test.txt\", \"w\") as f:\n",
|
|
" f.write(\"Hello, World!\")\n",
|
|
"\n",
|
|
"# Test with existing and non-existing files\n",
|
|
"file_processor(\"test.txt\")\n",
|
|
"print()\n",
|
|
"file_processor(\"nonexistent.txt\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 5. Raising Exceptions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Raising exceptions manually\n",
|
|
"def validate_age(age):\n",
|
|
" if not isinstance(age, (int, float)):\n",
|
|
" raise TypeError(f\"Age must be a number, got {type(age).__name__}\")\n",
|
|
" \n",
|
|
" if age < 0:\n",
|
|
" raise ValueError(\"Age cannot be negative\")\n",
|
|
" \n",
|
|
" if age > 150:\n",
|
|
" raise ValueError(\"Age seems unrealistic (over 150)\")\n",
|
|
" \n",
|
|
" print(f\"✅ Valid age: {age}\")\n",
|
|
" return age\n",
|
|
"\n",
|
|
"# Test age validation\n",
|
|
"test_ages = [25, -5, \"thirty\", 200]\n",
|
|
"\n",
|
|
"for age in test_ages:\n",
|
|
" try:\n",
|
|
" validate_age(age)\n",
|
|
" except (TypeError, ValueError) as e:\n",
|
|
" print(f\"❌ {type(e).__name__}: {e}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 6. Custom Exceptions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Creating custom exception classes\n",
|
|
"class BankingError(Exception):\n",
|
|
" \"\"\"Base exception for banking operations\"\"\"\n",
|
|
" def __init__(self, message, account_id=None):\n",
|
|
" super().__init__(message)\n",
|
|
" self.account_id = account_id\n",
|
|
"\n",
|
|
"class InsufficientFundsError(BankingError):\n",
|
|
" \"\"\"Raised when account has insufficient funds\"\"\"\n",
|
|
" def __init__(self, required, available, account_id):\n",
|
|
" message = f\"Need ${required}, have ${available}\"\n",
|
|
" super().__init__(message, account_id)\n",
|
|
" self.required = required\n",
|
|
" self.available = available\n",
|
|
"\n",
|
|
"class AccountFrozenError(BankingError):\n",
|
|
" \"\"\"Raised when account is frozen\"\"\"\n",
|
|
" pass\n",
|
|
"\n",
|
|
"# Banking system using custom exceptions\n",
|
|
"class BankAccount:\n",
|
|
" def __init__(self, account_id, balance=0):\n",
|
|
" self.account_id = account_id\n",
|
|
" self.balance = balance\n",
|
|
" self.is_frozen = False\n",
|
|
" \n",
|
|
" def withdraw(self, amount):\n",
|
|
" if self.is_frozen:\n",
|
|
" raise AccountFrozenError(f\"Account {self.account_id} is frozen\", self.account_id)\n",
|
|
" \n",
|
|
" if amount > self.balance:\n",
|
|
" raise InsufficientFundsError(amount, self.balance, self.account_id)\n",
|
|
" \n",
|
|
" self.balance -= amount\n",
|
|
" print(f\"✅ Withdrew ${amount}. New balance: ${self.balance}\")\n",
|
|
"\n",
|
|
"# Test the banking system\n",
|
|
"account = BankAccount(\"ACC001\", 100)\n",
|
|
"\n",
|
|
"try:\n",
|
|
" account.withdraw(50) # Should work\n",
|
|
" account.withdraw(100) # Should fail - insufficient funds\n",
|
|
"except BankingError as e:\n",
|
|
" print(f\"❌ Banking error: {e}\")\n",
|
|
" print(f\" Account: {e.account_id}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 7. Exception Chaining"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Exception chaining with 'from' keyword\n",
|
|
"class DataProcessingError(Exception):\n",
|
|
" pass\n",
|
|
"\n",
|
|
"def load_config(filename):\n",
|
|
" try:\n",
|
|
" with open(filename, 'r') as f:\n",
|
|
" import json\n",
|
|
" return json.loads(f.read())\n",
|
|
" except FileNotFoundError as e:\n",
|
|
" raise DataProcessingError(f\"Config file missing: {filename}\") from e\n",
|
|
" except json.JSONDecodeError as e:\n",
|
|
" raise DataProcessingError(f\"Invalid JSON in: {filename}\") from e\n",
|
|
"\n",
|
|
"def process_data(config_file):\n",
|
|
" try:\n",
|
|
" config = load_config(config_file)\n",
|
|
" print(f\"✅ Loaded config: {config}\")\n",
|
|
" except DataProcessingError as e:\n",
|
|
" print(f\"❌ Processing failed: {e}\")\n",
|
|
" print(f\" Original cause: {e.__cause__}\")\n",
|
|
"\n",
|
|
"# Create test files\n",
|
|
"import json\n",
|
|
"with open(\"valid.json\", \"w\") as f:\n",
|
|
" json.dump({\"setting\": \"value\"}, f)\n",
|
|
"\n",
|
|
"with open(\"invalid.json\", \"w\") as f:\n",
|
|
" f.write(\"{ invalid json }\")\n",
|
|
"\n",
|
|
"# Test exception chaining\n",
|
|
"process_data(\"valid.json\") # Should work\n",
|
|
"process_data(\"missing.json\") # FileNotFoundError -> DataProcessingError\n",
|
|
"process_data(\"invalid.json\") # JSONDecodeError -> DataProcessingError"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 8. Best Practices"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Best practices demonstration\n",
|
|
"import logging\n",
|
|
"from functools import wraps\n",
|
|
"\n",
|
|
"logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')\n",
|
|
"logger = logging.getLogger(__name__)\n",
|
|
"\n",
|
|
"# ✅ Good: Specific exception handling\n",
|
|
"def good_file_reader(filename):\n",
|
|
" try:\n",
|
|
" with open(filename, 'r') as f:\n",
|
|
" content = f.read()\n",
|
|
" logger.info(f\"Read {filename} successfully\")\n",
|
|
" return content\n",
|
|
" except FileNotFoundError:\n",
|
|
" logger.warning(f\"File not found: {filename}\")\n",
|
|
" return None\n",
|
|
" except PermissionError:\n",
|
|
" logger.error(f\"Permission denied: {filename}\")\n",
|
|
" return None\n",
|
|
" except Exception as e:\n",
|
|
" logger.error(f\"Unexpected error: {e}\")\n",
|
|
" raise # Re-raise unexpected errors\n",
|
|
"\n",
|
|
"# Exception handling decorator\n",
|
|
"def handle_exceptions(default_return=None, log_errors=True):\n",
|
|
" def decorator(func):\n",
|
|
" @wraps(func)\n",
|
|
" def wrapper(*args, **kwargs):\n",
|
|
" try:\n",
|
|
" return func(*args, **kwargs)\n",
|
|
" except Exception as e:\n",
|
|
" if log_errors:\n",
|
|
" logger.error(f\"Error in {func.__name__}: {e}\")\n",
|
|
" return default_return\n",
|
|
" return wrapper\n",
|
|
" return decorator\n",
|
|
"\n",
|
|
"@handle_exceptions(default_return=0)\n",
|
|
"def safe_divide(a, b):\n",
|
|
" return a / b\n",
|
|
"\n",
|
|
"# Test best practices\n",
|
|
"print(\"=== Testing Best Practices ===\")\n",
|
|
"result = good_file_reader(\"test.txt\")\n",
|
|
"print(f\"File content: {result[:20] if result else 'None'}...\")\n",
|
|
"\n",
|
|
"print(f\"Safe divide 10/2: {safe_divide(10, 2)}\")\n",
|
|
"print(f\"Safe divide 10/0: {safe_divide(10, 0)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 9. Debugging Exceptions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Using traceback for debugging\n",
|
|
"import traceback\n",
|
|
"import sys\n",
|
|
"\n",
|
|
"def debug_function():\n",
|
|
" def level_1():\n",
|
|
" return level_2()\n",
|
|
" \n",
|
|
" def level_2():\n",
|
|
" data = {\"key\": \"value\"}\n",
|
|
" return data[\"missing_key\"] # KeyError\n",
|
|
" \n",
|
|
" try:\n",
|
|
" level_1()\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❌ Exception: {e}\")\n",
|
|
" print(\"\\n🔍 Traceback:\")\n",
|
|
" traceback.print_exc()\n",
|
|
" \n",
|
|
" # Get exception info\n",
|
|
" exc_type, exc_value, exc_traceback = sys.exc_info()\n",
|
|
" print(f\"\\n📊 Exception details:\")\n",
|
|
" print(f\" Type: {exc_type.__name__}\")\n",
|
|
" print(f\" Value: {exc_value}\")\n",
|
|
"\n",
|
|
"debug_function()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Summary\n",
|
|
"\n",
|
|
"### Key Concepts Covered:\n",
|
|
"\n",
|
|
"🎯 **Basic Structure:**\n",
|
|
"- `try`: Code that might raise an exception\n",
|
|
"- `except`: Handle specific exceptions\n",
|
|
"- `else`: Runs only if no exception occurred\n",
|
|
"- `finally`: Always runs for cleanup\n",
|
|
"\n",
|
|
"🛠️ **Advanced Features:**\n",
|
|
"- Multiple exception handling\n",
|
|
"- Custom exception classes\n",
|
|
"- Exception chaining with `from`\n",
|
|
"- Re-raising exceptions with `raise`\n",
|
|
"\n",
|
|
"📋 **Best Practices:**\n",
|
|
"- Use specific exception types\n",
|
|
"- Don't suppress exceptions silently\n",
|
|
"- Log errors appropriately\n",
|
|
"- Clean up resources properly\n",
|
|
"- Create meaningful custom exceptions\n",
|
|
"\n",
|
|
"### Exception Hierarchy (Common Types):\n",
|
|
"```\n",
|
|
"Exception\n",
|
|
" ├── ArithmeticError\n",
|
|
" │ └── ZeroDivisionError\n",
|
|
" ├── AttributeError\n",
|
|
" ├── LookupError\n",
|
|
" │ ├── IndexError\n",
|
|
" │ └── KeyError\n",
|
|
" ├── NameError\n",
|
|
" ├── OSError\n",
|
|
" │ ├── FileNotFoundError\n",
|
|
" │ └── PermissionError\n",
|
|
" ├── TypeError\n",
|
|
" └── ValueError\n",
|
|
"```\n",
|
|
"\n",
|
|
"### Remember:\n",
|
|
"- **Catch specific exceptions** rather than using broad `except Exception`\n",
|
|
"- **Always clean up resources** using `finally` or context managers\n",
|
|
"- **Log errors meaningfully** to help with debugging\n",
|
|
"- **Don't hide failures** - make them visible and actionable"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "venv",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.13.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|