Exception Handling

Back

Loading concept...

🛡️ Error Handling: Your Code’s Safety Net

The Big Picture: Why Errors Happen

Imagine you’re a chef in a kitchen. Everything is going great—until someone asks for a dish you’ve never made. Or the oven breaks. Or you run out of ingredients.

What do you do? You don’t just freeze! You have a plan:

  • If the oven breaks → use the stovetop
  • If you’re missing an ingredient → find a substitute
  • No matter what → clean up the kitchen at the end

That’s exactly what Exception Handling does for your code! It’s your safety net that catches problems before they crash your program.


🎯 What You’ll Learn

graph LR A["Exception Handling"] --> B["try-except Blocks"] A --> C["Multiple Exceptions"] A --> D["else Clause"] A --> E["finally Clause"] A --> F["Raising Exceptions"] A --> G["Exception Chaining"] A --> H["Custom Exceptions"] A --> I["Built-in Exceptions"]

đź§± 1. try-except Blocks: The Foundation

What’s the Story?

Think of try-except like a protective bubble. You put risky code inside the bubble. If something goes wrong, the bubble catches it instead of letting it break everything.

The Simple Pattern

try:
    # Risky code goes here
    result = 10 / 0
except:
    # What to do if it fails
    print("Oops! Something broke!")

Real Example: Dividing Numbers

try:
    number = int(input("Enter a number: "))
    result = 100 / number
    print(f"100 divided by {number} = {result}")
except:
    print("That didn't work!")

What happens:

  • User enters 5 → prints 20.0 âś…
  • User enters 0 → prints “That didn’t work!” âś…
  • User enters abc → prints “That didn’t work!” âś…

Catching Specific Errors

Be a detective! Catch the exact error:

try:
    number = int(input("Enter a number: "))
    result = 100 / number
except ZeroDivisionError:
    print("You can't divide by zero!")
except ValueError:
    print("That's not a number!")

Now you know exactly what went wrong!


🎯 2. Catching Multiple Exceptions

The Story

Sometimes, different things can go wrong. Like a doctor who can treat both a cold AND a broken arm—your code can handle multiple problems!

Method 1: Separate except Blocks

try:
    file = open("data.txt")
    number = int(file.read())
    result = 100 / number
except FileNotFoundError:
    print("File doesn't exist!")
except ValueError:
    print("File doesn't have a number!")
except ZeroDivisionError:
    print("Can't divide by zero!")

Method 2: Group Similar Errors

When you want the same response for different errors:

try:
    value = int(input("Enter age: "))
    result = 100 / value
except (ValueError, TypeError):
    print("Need a valid number!")
except ZeroDivisionError:
    print("Age can't be zero!")

Getting Error Details

Want to know exactly what went wrong?

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Error details: {e}")
    # Output: Error details: division by zero

✨ 3. The else Clause: Celebrate Success!

The Story

Imagine you’re baking a cake. The try block is mixing ingredients. The except block handles disasters. But what if everything goes perfectly? That’s when else runs—only when nothing went wrong!

Pattern

try:
    # Risky stuff
except SomeError:
    # Handle problems
else:
    # Only runs if NO errors happened

Real Example

try:
    age = int(input("Enter your age: "))
except ValueError:
    print("That's not a number!")
else:
    print(f"In 10 years, you'll be {age + 10}!")

Why Use else?

Without else:

try:
    number = int(input("Number: "))
    print(f"Double: {number * 2}")  # This runs even if next line fails
except ValueError:
    print("Invalid!")

With else (cleaner!):

try:
    number = int(input("Number: "))
except ValueError:
    print("Invalid!")
else:
    print(f"Double: {number * 2}")  # Only runs if try succeeded

đź”’ 4. The finally Clause: Always Do This!

The Story

Imagine you’re borrowing a friend’s toy. No matter what happens—whether you play with it or accidentally break it—you must return it. That’s finally!

Pattern

try:
    # Try something risky
except SomeError:
    # Handle the error
finally:
    # THIS ALWAYS RUNS no matter what!

Classic Example: Closing Files

file = None
try:
    file = open("data.txt", "r")
    content = file.read()
    print(content)
except FileNotFoundError:
    print("File not found!")
finally:
    if file:
        file.close()
        print("File closed!")

finally Runs Even When…

âś… The code works perfectly âś… An error happens âś… You use return inside try âś… You use break in a loop

def get_number():
    try:
        return int("abc")  # This fails
    except ValueError:
        return 0  # Returns 0
    finally:
        print("Cleanup!")  # Still prints!

result = get_number()
# Output: Cleanup!
# result = 0

🚀 5. Raising Exceptions: Sound the Alarm!

The Story

Sometimes YOU need to signal a problem. Like a lifeguard blowing a whistle when they see danger. You raise an exception to say “STOP! Something’s wrong!”

Basic Pattern

raise ExceptionType("Your message here")

Example: Validating Input

def set_age(age):
    if age < 0:
        raise ValueError("Age can't be negative!")
    if age > 150:
        raise ValueError("That's too old!")
    return age

# Usage:
try:
    my_age = set_age(-5)
except ValueError as e:
    print(f"Problem: {e}")
# Output: Problem: Age can't be negative!

Re-raising Exceptions

Sometimes you want to handle an error AND pass it along:

def process_data(data):
    try:
        result = int(data)
    except ValueError:
        print("Logging: Bad data received")
        raise  # Re-raise the same error!

try:
    process_data("abc")
except ValueError:
    print("Caller handles it too!")

đź”— 6. Exception Chaining: Tell the Full Story

The Story

Imagine a detective solving a case. The crime (error) you see might be caused by an earlier crime. Exception chaining connects them so you see the whole story.

Pattern: from Keyword

try:
    value = int("abc")
except ValueError as original:
    raise TypeError("Can't process data") from original

Real Example

def load_config(filename):
    try:
        with open(filename) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError("Config failed!") from e

try:
    config = load_config("missing.txt")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Caused by: {e.__cause__}")

Output:

Error: Config failed!
Caused by: [Errno 2] No such file or directory: 'missing.txt'

Hiding the Chain

Sometimes you want to hide the original error:

raise NewError("Message") from None

🎨 7. Custom Exceptions: Your Own Error Types

The Story

Built-in exceptions are like generic medicine. But sometimes you need a specific solution. Custom exceptions let you create error types that make sense for YOUR program!

Creating a Custom Exception

class AgeError(Exception):
    """Raised when age is invalid"""
    pass

def check_age(age):
    if age < 0:
        raise AgeError("Age cannot be negative!")
    if age < 18:
        raise AgeError("Must be 18 or older!")
    return True

Adding More Features

class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        self.needed = amount - balance
        message = f"Need ${self.needed} more!"
        super().__init__(message)

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

try:
    withdraw(50, 100)
except InsufficientFundsError as e:
    print(f"Error: {e}")
    print(f"You have: ${e.balance}")
    print(f"You need: ${e.needed} more")

Best Practice: Exception Hierarchy

class GameError(Exception):
    """Base for all game errors"""
    pass

class PlayerError(GameError):
    """Player-related errors"""
    pass

class InventoryError(GameError):
    """Inventory-related errors"""
    pass

# Now you can catch all game errors:
try:
    # game code
    pass
except GameError:
    # Catches PlayerError AND InventoryError
    pass

📚 8. Common Built-in Exceptions

The Most Common Ones

Exception When It Happens Example
ValueError Wrong value type int("abc")
TypeError Wrong data type "2" + 2
IndexError List index out of range [1,2,3][10]
KeyError Dictionary key missing {"a":1}["b"]
FileNotFoundError File doesn’t exist open("missing.txt")
ZeroDivisionError Dividing by zero 10 / 0
AttributeError Object lacks attribute "hi".push()
NameError Variable not defined print(xyz)

Exception Hierarchy

graph TD A["BaseException"] --> B["Exception"] B --> C["ValueError"] B --> D["TypeError"] B --> E["KeyError"] B --> F["IndexError"] B --> G["FileNotFoundError"] B --> H["ZeroDivisionError"] A --> I["SystemExit"] A --> J["KeyboardInterrupt"]

Quick Examples

# ValueError
try:
    int("hello")
except ValueError:
    print("Can't convert to number!")

# KeyError
try:
    data = {"name": "Alex"}
    print(data["age"])
except KeyError:
    print("Key doesn't exist!")

# IndexError
try:
    colors = ["red", "blue"]
    print(colors[5])
except IndexError:
    print("Index out of range!")

# AttributeError
try:
    number = 42
    number.append(1)
except AttributeError:
    print("Numbers don't have append!")

🏆 Putting It All Together

Here’s a complete example using everything you learned:

class WithdrawalError(Exception):
    """Custom error for bank withdrawals"""
    pass

def withdraw_money(balance, amount):
    try:
        # Validate input
        if not isinstance(amount, (int, float)):
            raise TypeError("Amount must be a number!")
        if amount <= 0:
            raise ValueError("Amount must be positive!")
        if amount > balance:
            raise WithdrawalError(
                f"Not enough funds! Balance: ${balance}"
            )

        # Process withdrawal
        new_balance = balance - amount

    except TypeError as e:
        print(f"Type error: {e}")
        raise
    except ValueError as e:
        print(f"Value error: {e}")
        raise
    else:
        print(f"Success! New balance: ${new_balance}")
        return new_balance
    finally:
        print("Transaction logged.")

# Test it:
try:
    result = withdraw_money(100, 50)
except Exception as e:
    print(f"Transaction failed: {e}")

🎯 Key Takeaways

  1. try-except = Your safety net for risky code
  2. Multiple except = Handle different errors differently
  3. else = Runs only when try succeeds
  4. finally = Always runs, no matter what
  5. raise = You create the error when needed
  6. Chaining = Connect related errors
  7. Custom exceptions = Your own error types
  8. Built-ins = Know the common ones!

💪 You’ve Got This!

Exception handling isn’t scary—it’s your superpower! With these tools, your programs will:

  • Never crash unexpectedly âś…
  • Give helpful error messages âś…
  • Clean up properly âś…
  • Handle anything users throw at them âś…

Now go build something amazing—and don’t be afraid of errors. You know how to handle them! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.