+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 262 of 365

๐Ÿ“˜ Multiple Exceptions: Handling Different Errors

Master multiple exceptions: handling different errors in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on handling multiple exceptions in Python! ๐ŸŽ‰ Have you ever wondered how to gracefully handle different types of errors in your programs? Today, weโ€™ll explore the art of catching and managing multiple exceptions like a pro!

Youโ€™ll discover how handling multiple exceptions can make your Python applications more robust and user-friendly. Whether youโ€™re building web applications ๐ŸŒ, processing data ๐Ÿ“Š, or creating automation scripts ๐Ÿค–, understanding multiple exception handling is essential for writing bulletproof code.

By the end of this tutorial, youโ€™ll feel confident handling any combination of errors that come your way! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Multiple Exceptions

๐Ÿค” What are Multiple Exceptions?

Handling multiple exceptions is like being a skilled juggler ๐Ÿคน - you need to catch different types of balls (errors) and handle each one appropriately. Think of it as having different safety nets for different circus acts!

In Python terms, multiple exception handling allows you to catch and respond to different error types in a single try block. This means you can:

  • โœจ Handle different errors with specific responses
  • ๐Ÿš€ Keep your code running smoothly
  • ๐Ÿ›ก๏ธ Provide meaningful feedback to users

๐Ÿ’ก Why Handle Multiple Exceptions?

Hereโ€™s why developers love multiple exception handling:

  1. Specific Error Handling ๐ŸŽฏ: Respond appropriately to each error type
  2. Better User Experience ๐Ÿ’ป: Provide helpful error messages
  3. Code Resilience ๐Ÿ›ก๏ธ: Prevent crashes from unexpected errors
  4. Cleaner Code ๐Ÿงน: Organize error handling logically

Real-world example: Imagine building a file processor ๐Ÿ“. You might encounter file not found errors, permission errors, or disk full errors - each needing different handling!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a friendly example:

# ๐Ÿ‘‹ Hello, Multiple Exceptions!
def divide_numbers(a, b):
    try:
        # ๐ŸŽฏ The risky operation
        result = a / b
        print(f"Result: {result} โœจ")
        return result
    except ZeroDivisionError:
        # ๐Ÿšซ Handle division by zero
        print("Oops! Can't divide by zero! ๐Ÿคฏ")
        return None
    except TypeError:
        # โŒ Handle wrong data types
        print("Please provide numbers only! ๐Ÿ”ข")
        return None

# ๐ŸŽฎ Let's test it!
divide_numbers(10, 2)    # Works great!
divide_numbers(10, 0)    # Catches zero division
divide_numbers(10, "2")  # Catches type error

๐Ÿ’ก Explanation: Notice how we handle each exception type separately! This gives us precise control over error responses.

๐ŸŽฏ Common Patterns

Here are patterns youโ€™ll use daily:

# ๐Ÿ—๏ธ Pattern 1: Multiple except blocks
def process_user_input(data):
    try:
        # ๐ŸŽจ Process the data
        value = int(data)
        result = 100 / value
        return f"Success! Result is {result} ๐ŸŽ‰"
    except ValueError:
        # ๐Ÿ“ Handle conversion errors
        return "Please enter a valid number! ๐Ÿ”ข"
    except ZeroDivisionError:
        # ๐Ÿšซ Handle division by zero
        return "Zero is not allowed! ๐Ÿšซ"
    except Exception as e:
        # ๐Ÿ›ก๏ธ Catch-all for unexpected errors
        return f"Something went wrong: {e} ๐Ÿ˜…"

# ๐ŸŽจ Pattern 2: Catching multiple exceptions together
def read_config_file(filename):
    try:
        # ๐Ÿ“– Try to read the file
        with open(filename, 'r') as f:
            return f.read()
    except (FileNotFoundError, PermissionError) as e:
        # ๐Ÿ“ Handle file-related errors together
        print(f"File error: {e} ๐Ÿ“")
        return None

# ๐Ÿ”„ Pattern 3: Exception hierarchy
def safe_operation(data):
    try:
        # ๐ŸŽฏ Some risky operation
        process_data(data)
    except KeyboardInterrupt:
        # โŒจ๏ธ Always catch this first!
        print("Operation cancelled by user! ๐Ÿ›‘")
        raise  # Re-raise to allow proper cleanup
    except Exception as e:
        # ๐Ÿ›ก๏ธ General exception handler
        print(f"Error occurred: {e} ๐Ÿ˜”")

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Online Shopping Cart

Letโ€™s build something real:

# ๐Ÿ›๏ธ Define our shopping cart with error handling
class ShoppingCart:
    def __init__(self):
        self.items = {}  # ๐Ÿ›’ Our cart storage
        self.max_items = 10  # ๐Ÿ“ฆ Cart limit
    
    def add_item(self, item_name, price, quantity):
        """Add item to cart with multiple error checks! ๐ŸŽฏ"""
        try:
            # ๐Ÿ” Validate inputs
            if not isinstance(item_name, str):
                raise TypeError("Item name must be text! ๐Ÿ“")
            
            if price <= 0:
                raise ValueError("Price must be positive! ๐Ÿ’ฐ")
            
            if quantity <= 0:
                raise ValueError("Quantity must be at least 1! ๐Ÿ”ข")
            
            if len(self.items) >= self.max_items:
                raise OverflowError("Cart is full! ๐Ÿ“ฆ")
            
            # โœจ Add to cart
            self.items[item_name] = {
                'price': float(price),
                'quantity': int(quantity),
                'emoji': self._get_item_emoji(item_name)
            }
            
            print(f"Added {quantity}x {item_name} to cart! {self._get_item_emoji(item_name)}")
            
        except TypeError as e:
            print(f"โŒ Type Error: {e}")
        except ValueError as e:
            print(f"โš ๏ธ Value Error: {e}")
        except OverflowError as e:
            print(f"๐Ÿšซ Cart Error: {e}")
        except Exception as e:
            print(f"๐Ÿ˜ฑ Unexpected error: {e}")
    
    def _get_item_emoji(self, item_name):
        """Get emoji for items! ๐ŸŽจ"""
        emojis = {
            'apple': '๐ŸŽ', 'banana': '๐ŸŒ', 'coffee': 'โ˜•',
            'pizza': '๐Ÿ•', 'book': '๐Ÿ“š', 'laptop': '๐Ÿ’ป'
        }
        return emojis.get(item_name.lower(), '๐Ÿ“ฆ')
    
    def checkout(self):
        """Calculate total with error handling! ๐Ÿ’ฐ"""
        try:
            if not self.items:
                raise ValueError("Cart is empty! ๐Ÿ›’")
            
            total = sum(item['price'] * item['quantity'] 
                       for item in self.items.values())
            
            print(f"\n๐Ÿงพ Your cart total: ${total:.2f}")
            return total
            
        except ValueError as e:
            print(f"โš ๏ธ Checkout Error: {e}")
            return 0
        except Exception as e:
            print(f"๐Ÿ’ฅ Checkout failed: {e}")
            return 0

# ๐ŸŽฎ Let's use our cart!
cart = ShoppingCart()

# โœ… Valid additions
cart.add_item("Apple", 1.99, 3)
cart.add_item("Coffee", 4.99, 1)

# โŒ Invalid additions (will be caught!)
cart.add_item(123, 2.99, 1)         # Wrong type
cart.add_item("Banana", -1.00, 2)   # Negative price
cart.add_item("Pizza", 9.99, 0)     # Zero quantity

# ๐Ÿ’ฐ Checkout
cart.checkout()

๐ŸŽฏ Try it yourself: Add a remove_item method with its own exception handling!

๐ŸŽฎ Example 2: Game Save System

Letโ€™s make it fun with a game save system:

import json
import os

# ๐Ÿ† Game save system with robust error handling
class GameSaveManager:
    def __init__(self, save_directory="game_saves"):
        self.save_dir = save_directory
        self._ensure_save_directory()
    
    def _ensure_save_directory(self):
        """Create save directory if needed! ๐Ÿ“"""
        try:
            os.makedirs(self.save_dir, exist_ok=True)
        except PermissionError:
            print("โš ๏ธ No permission to create save directory!")
        except Exception as e:
            print(f"๐Ÿ“ Directory error: {e}")
    
    def save_game(self, player_name, game_data):
        """Save game with multiple error checks! ๐Ÿ’พ"""
        try:
            # ๐Ÿ” Validate player name
            if not player_name or not isinstance(player_name, str):
                raise ValueError("Invalid player name! ๐Ÿ‘ค")
            
            # ๐ŸŽฎ Validate game data
            if not isinstance(game_data, dict):
                raise TypeError("Game data must be a dictionary! ๐Ÿ“–")
            
            # ๐Ÿ“ Add metadata
            save_data = {
                'player': player_name,
                'level': game_data.get('level', 1),
                'score': game_data.get('score', 0),
                'achievements': game_data.get('achievements', []),
                'emoji': '๐ŸŽฎ'
            }
            
            # ๐Ÿ’พ Save to file
            filename = os.path.join(self.save_dir, f"{player_name}_save.json")
            with open(filename, 'w') as f:
                json.dump(save_data, f, indent=2)
            
            print(f"โœ… Game saved for {player_name}! ๐ŸŽฎ")
            return True
            
        except ValueError as e:
            print(f"โŒ Invalid input: {e}")
        except TypeError as e:
            print(f"โŒ Type error: {e}")
        except PermissionError:
            print("๐Ÿšซ No permission to save file!")
        except IOError:
            print("๐Ÿ’พ Disk error - could not save!")
        except Exception as e:
            print(f"๐Ÿ˜ฑ Unexpected save error: {e}")
        
        return False
    
    def load_game(self, player_name):
        """Load game with error recovery! ๐Ÿ“‚"""
        try:
            filename = os.path.join(self.save_dir, f"{player_name}_save.json")
            
            # ๐Ÿ“– Try to load the save
            with open(filename, 'r') as f:
                save_data = json.load(f)
            
            print(f"โœ… Loaded save for {player_name}!")
            print(f"๐Ÿ“Š Level: {save_data['level']}, Score: {save_data['score']}")
            return save_data
            
        except FileNotFoundError:
            print(f"๐Ÿ” No save found for {player_name}")
            return self._create_new_save(player_name)
        except json.JSONDecodeError:
            print("๐Ÿ’ฅ Save file corrupted! Creating backup...")
            self._backup_corrupted_save(filename)
            return self._create_new_save(player_name)
        except PermissionError:
            print("๐Ÿšซ No permission to read save file!")
        except Exception as e:
            print(f"๐Ÿ˜ฑ Load error: {e}")
        
        return None
    
    def _create_new_save(self, player_name):
        """Create a fresh save! ๐ŸŒŸ"""
        return {
            'player': player_name,
            'level': 1,
            'score': 0,
            'achievements': ['๐ŸŒŸ First Steps'],
            'emoji': '๐ŸŽฎ'
        }
    
    def _backup_corrupted_save(self, filename):
        """Backup corrupted saves! ๐Ÿ›ก๏ธ"""
        try:
            backup_name = f"{filename}.corrupted"
            os.rename(filename, backup_name)
            print(f"๐Ÿ“ฆ Corrupted save backed up to {backup_name}")
        except:
            pass  # Silently fail backup

# ๐ŸŽฎ Let's test our save system!
save_manager = GameSaveManager()

# โœ… Valid saves
save_manager.save_game("Alice", {'level': 5, 'score': 1000})
save_manager.save_game("Bob", {'level': 3, 'score': 500, 'achievements': ['๐Ÿ† First Boss']})

# โŒ Invalid saves (handled gracefully!)
save_manager.save_game("", {'level': 1})          # Empty name
save_manager.save_game("Charlie", "not a dict")   # Wrong type

# ๐Ÿ“‚ Load games
save_manager.load_game("Alice")      # Existing save
save_manager.load_game("NewPlayer")  # Non-existent save

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Custom Exception Classes

When youโ€™re ready to level up, create your own exceptions:

# ๐ŸŽฏ Custom exceptions for a banking app
class BankingError(Exception):
    """Base exception for banking operations ๐Ÿฆ"""
    pass

class InsufficientFundsError(BankingError):
    """Not enough money! ๐Ÿ’ธ"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"Cannot withdraw ${amount} from balance ${balance}")

class AccountLockedError(BankingError):
    """Account is locked! ๐Ÿ”’"""
    pass

class InvalidTransactionError(BankingError):
    """Invalid transaction! โŒ"""
    pass

# ๐Ÿฆ Banking system using custom exceptions
class BankAccount:
    def __init__(self, account_number, initial_balance=0):
        self.account_number = account_number
        self.balance = initial_balance
        self.locked = False
        self.transactions = []
    
    def withdraw(self, amount):
        """Withdraw with custom exception handling! ๐Ÿ’ฐ"""
        try:
            # ๐Ÿ”’ Check if locked
            if self.locked:
                raise AccountLockedError("Account is locked! ๐Ÿ”’")
            
            # ๐Ÿ’ต Check amount validity
            if amount <= 0:
                raise InvalidTransactionError("Amount must be positive! โž•")
            
            # ๐Ÿ’ธ Check sufficient funds
            if amount > self.balance:
                raise InsufficientFundsError(self.balance, amount)
            
            # โœ… Process withdrawal
            self.balance -= amount
            self.transactions.append(f"Withdrew ${amount} ๐Ÿ’ธ")
            print(f"โœ… Withdrew ${amount}. New balance: ${self.balance}")
            
        except AccountLockedError:
            print("๐Ÿ”’ Cannot withdraw - account is locked!")
        except InsufficientFundsError as e:
            print(f"๐Ÿ’ธ Insufficient funds! You have ${e.balance} but need ${e.amount}")
        except InvalidTransactionError as e:
            print(f"โŒ Invalid transaction: {e}")
        except Exception as e:
            print(f"๐Ÿ˜ฑ Unexpected error: {e}")

# ๐ŸŽฎ Test our banking system!
account = BankAccount("123456", 100)

account.withdraw(50)    # โœ… Works!
account.withdraw(200)   # ๐Ÿ’ธ Insufficient funds!
account.withdraw(-10)   # โŒ Invalid amount!

account.locked = True
account.withdraw(10)    # ๐Ÿ”’ Account locked!

๐Ÿ—๏ธ Advanced Topic 2: Exception Chaining and Context

For the brave developers, letโ€™s explore exception chaining:

# ๐Ÿš€ Advanced exception chaining
class DataProcessor:
    def __init__(self):
        self.data = []
        self.processed = []
    
    def load_data(self, source):
        """Load data with exception chaining! ๐Ÿ“Š"""
        try:
            if source == "database":
                # ๐Ÿ—„๏ธ Simulate database loading
                raise ConnectionError("Database unavailable! ๐Ÿ”Œ")
            elif source == "file":
                # ๐Ÿ“ Simulate file loading
                with open("data.txt", 'r') as f:
                    self.data = f.readlines()
            else:
                raise ValueError(f"Unknown source: {source}")
                
        except FileNotFoundError as e:
            # ๐Ÿ”— Chain exceptions for context
            raise RuntimeError("Data loading failed! ๐Ÿ“") from e
        except ConnectionError as e:
            # ๐Ÿ”— Provide alternative suggestion
            print(f"โš ๏ธ {e} - Trying backup source...")
            try:
                self.load_data("file")  # Try alternative
            except Exception as backup_error:
                raise RuntimeError("All data sources failed! ๐Ÿ˜ฑ") from backup_error
    
    def process_data(self):
        """Process with context managers! ๐ŸŽจ"""
        class ProcessingContext:
            def __enter__(self):
                print("๐ŸŽฌ Starting processing...")
                return self
            
            def __exit__(self, exc_type, exc_val, exc_tb):
                if exc_type is None:
                    print("โœ… Processing completed successfully!")
                else:
                    print(f"โŒ Processing failed: {exc_val}")
                    # Return False to propagate the exception
                return False
        
        try:
            with ProcessingContext():
                if not self.data:
                    raise ValueError("No data to process! ๐Ÿ“Š")
                
                # ๐ŸŽจ Process each item
                for item in self.data:
                    if "error" in item.lower():
                        raise RuntimeError(f"Found error in data: {item.strip()}")
                    self.processed.append(item.upper())
                
        except ValueError as e:
            print(f"โš ๏ธ Data validation error: {e}")
        except RuntimeError as e:
            print(f"๐Ÿ’ฅ Processing error: {e}")
        except Exception as e:
            print(f"๐Ÿ˜ฑ Unexpected error: {e}")

# ๐ŸŽฎ Test advanced features!
processor = DataProcessor()

# Try different scenarios
try:
    processor.load_data("database")  # Will fail and try backup
except RuntimeError as e:
    print(f"Final error: {e}")
    print(f"Original cause: {e.__cause__}")

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Catching Exceptions Too Broadly

# โŒ Wrong way - catches everything!
try:
    result = risky_operation()
except:  # ๐Ÿ˜ฐ This catches EVERYTHING including KeyboardInterrupt!
    print("Something went wrong")

# โœ… Correct way - be specific!
try:
    result = risky_operation()
except ValueError:
    print("Invalid value provided! ๐Ÿ“")
except IOError:
    print("File operation failed! ๐Ÿ“")
except Exception as e:  # ๐Ÿ›ก๏ธ Catch other exceptions but not system exits
    print(f"Unexpected error: {e}")

๐Ÿคฏ Pitfall 2: Wrong Exception Order

# โŒ Dangerous - specific exceptions never caught!
try:
    process_file("data.txt")
except Exception:  # ๐Ÿ˜ฑ This catches everything first!
    print("General error")
except FileNotFoundError:  # ๐Ÿ’ฅ Never reached!
    print("File not found")

# โœ… Safe - specific exceptions first!
try:
    process_file("data.txt")
except FileNotFoundError:  # ๐ŸŽฏ Specific first
    print("File not found! ๐Ÿ“")
except PermissionError:    # ๐Ÿ”’ Then other specific
    print("No permission! ๐Ÿšซ")
except Exception as e:     # ๐Ÿ›ก๏ธ General last
    print(f"Other error: {e}")

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Be Specific: Catch specific exceptions, not bare except:
  2. ๐Ÿ“ Order Matters: Put specific exceptions before general ones
  3. ๐Ÿ›ก๏ธ Always Have a Safety Net: Use Exception as last resort
  4. ๐ŸŽจ Create Custom Exceptions: For domain-specific errors
  5. โœจ Provide Context: Give helpful error messages to users

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a File Processing System

Create a robust file processor that handles multiple error scenarios:

๐Ÿ“‹ Requirements:

  • โœ… Read files with various formats (txt, json, csv)
  • ๐Ÿท๏ธ Handle file not found, permission, and format errors
  • ๐Ÿ‘ค Parse and validate data from files
  • ๐Ÿ“Š Generate error reports for failures
  • ๐ŸŽจ Each operation needs specific error handling!

๐Ÿš€ Bonus Points:

  • Add retry logic for temporary failures
  • Create custom exceptions for validation errors
  • Implement logging for all errors

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
import json
import csv
import time
from datetime import datetime

# ๐ŸŽฏ Custom exceptions for our file processor
class FileProcessingError(Exception):
    """Base exception for file processing ๐Ÿ“"""
    pass

class ValidationError(FileProcessingError):
    """Data validation failed! โŒ"""
    pass

class UnsupportedFormatError(FileProcessingError):
    """File format not supported! ๐Ÿ“„"""
    pass

# ๐Ÿ“Š Our robust file processor!
class FileProcessor:
    def __init__(self):
        self.supported_formats = ['.txt', '.json', '.csv']
        self.processed_files = []
        self.error_log = []
    
    def process_file(self, filename, retry_count=3):
        """Process file with multiple error handlers! ๐ŸŽฏ"""
        for attempt in range(retry_count):
            try:
                # ๐Ÿ” Check file extension
                if not any(filename.endswith(fmt) for fmt in self.supported_formats):
                    raise UnsupportedFormatError(f"Format not supported: {filename}")
                
                # ๐Ÿ“– Read and process based on type
                if filename.endswith('.json'):
                    data = self._process_json(filename)
                elif filename.endswith('.csv'):
                    data = self._process_csv(filename)
                else:
                    data = self._process_text(filename)
                
                # โœ… Validate data
                self._validate_data(data, filename)
                
                # ๐ŸŽ‰ Success!
                self.processed_files.append({
                    'filename': filename,
                    'records': len(data) if isinstance(data, list) else 1,
                    'timestamp': datetime.now().isoformat(),
                    'status': 'โœ…'
                })
                
                print(f"โœ… Successfully processed {filename}!")
                return data
                
            except FileNotFoundError:
                self._log_error(filename, "File not found! ๐Ÿ”")
                print(f"โŒ File not found: {filename}")
                break  # Don't retry for missing files
                
            except PermissionError:
                self._log_error(filename, "Permission denied! ๐Ÿ”’")
                print(f"๐Ÿšซ No permission to read: {filename}")
                break  # Don't retry for permission issues
                
            except json.JSONDecodeError as e:
                self._log_error(filename, f"Invalid JSON: {e} ๐Ÿ“‹")
                print(f"โŒ JSON parsing error in {filename}")
                break  # Don't retry for format errors
                
            except UnsupportedFormatError as e:
                self._log_error(filename, str(e))
                print(f"๐Ÿ“„ {e}")
                break
                
            except ValidationError as e:
                self._log_error(filename, f"Validation failed: {e} โš ๏ธ")
                print(f"โš ๏ธ Validation error in {filename}: {e}")
                break
                
            except IOError as e:
                self._log_error(filename, f"IO Error: {e} ๐Ÿ’พ")
                if attempt < retry_count - 1:
                    print(f"๐Ÿ’พ IO Error, retrying in 1 second... (Attempt {attempt + 1}/{retry_count})")
                    time.sleep(1)
                else:
                    print(f"๐Ÿ’ฅ Failed after {retry_count} attempts!")
                    
            except Exception as e:
                self._log_error(filename, f"Unexpected: {e} ๐Ÿ˜ฑ")
                print(f"๐Ÿ˜ฑ Unexpected error processing {filename}: {e}")
                break
        
        return None
    
    def _process_json(self, filename):
        """Process JSON files! ๐Ÿ“‹"""
        with open(filename, 'r') as f:
            return json.load(f)
    
    def _process_csv(self, filename):
        """Process CSV files! ๐Ÿ“Š"""
        data = []
        with open(filename, 'r') as f:
            reader = csv.DictReader(f)
            for row in reader:
                data.append(row)
        return data
    
    def _process_text(self, filename):
        """Process text files! ๐Ÿ“"""
        with open(filename, 'r') as f:
            return f.readlines()
    
    def _validate_data(self, data, filename):
        """Validate processed data! ๐Ÿ”"""
        if not data:
            raise ValidationError("File is empty!")
        
        if isinstance(data, list) and len(data) > 1000:
            raise ValidationError("File too large (>1000 records)!")
    
    def _log_error(self, filename, error_msg):
        """Log errors for reporting! ๐Ÿ“"""
        self.error_log.append({
            'filename': filename,
            'error': error_msg,
            'timestamp': datetime.now().isoformat()
        })
    
    def generate_report(self):
        """Generate processing report! ๐Ÿ“Š"""
        print("\n๐Ÿ“Š File Processing Report")
        print("=" * 40)
        
        print(f"\nโœ… Successfully processed: {len(self.processed_files)} files")
        for file_info in self.processed_files:
            print(f"  ๐Ÿ“ {file_info['filename']} ({file_info['records']} records)")
        
        print(f"\nโŒ Failed to process: {len(self.error_log)} files")
        for error_info in self.error_log:
            print(f"  โš ๏ธ {error_info['filename']}: {error_info['error']}")

# ๐ŸŽฎ Test our file processor!
processor = FileProcessor()

# Test various scenarios
test_files = [
    "data.json",       # May or may not exist
    "config.txt",      # May or may not exist
    "records.csv",     # May or may not exist
    "image.png",       # Unsupported format
    "protected.txt"    # May have permission issues
]

for filename in test_files:
    processor.process_file(filename)

# ๐Ÿ“Š Generate final report
processor.generate_report()

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Handle multiple exceptions with confidence ๐Ÿ’ช
  • โœ… Create specific error responses for different scenarios ๐ŸŽฏ
  • โœ… Build robust applications that donโ€™t crash easily ๐Ÿ›ก๏ธ
  • โœ… Debug issues by understanding error types ๐Ÿ›
  • โœ… Write professional code with proper error handling! ๐Ÿš€

Remember: Good error handling is like wearing a seatbelt - you hope you donโ€™t need it, but youโ€™ll be glad itโ€™s there! ๐Ÿš—

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered handling multiple exceptions in Python!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the exercises above
  2. ๐Ÿ—๏ธ Add robust error handling to your existing projects
  3. ๐Ÿ“š Move on to our next tutorial: Exception Chaining and Context
  4. ๐ŸŒŸ Share your error-handling victories with others!

Remember: Every Python expert has encountered countless errors. The difference is they learned to handle them gracefully! Keep coding, keep learning, and most importantly, have fun! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ