{ "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 }