Python - User-Defined Exceptions

Hello there, future Python wizards! Today, we're going to embark on an exciting journey into the world of user-defined exceptions in Python. Don't worry if you're new to programming; I'll guide you through this adventure step by step, just like I've done for countless students over my years of teaching. So, grab your virtual wands (keyboards), and let's dive in!

Python - User-defined Exception

User-Defined Exceptions in Python

Before we start creating our own exceptions, let's quickly recap what exceptions are. Imagine you're cooking a delicious meal, but suddenly you realize you're out of a crucial ingredient. That's similar to an exception in programming – it's an unexpected situation that disrupts the normal flow of your code.

Python comes with many built-in exceptions, like ValueError, TypeError, and ZeroDivisionError. But sometimes, we need to create our own special exceptions to handle unique situations in our programs. That's where user-defined exceptions come in handy!

How to Create a User-Defined Exception

Creating your own exception is as easy as baking a cake (well, an easy cake recipe). All you need to do is create a new class that inherits from the built-in Exception class or any of its subclasses. Let's look at a simple example:

class MySpecialError(Exception):
    pass

That's it! You've just created your first user-defined exception. The pass statement is used because we don't need to add any additional functionality to our exception class.

But what if we want our exception to be a bit more informative? Let's create another one:

class ValueTooLargeError(Exception):
    def __init__(self, message, value):
        self.message = message
        self.value = value

In this example, we've added an __init__ method to our exception class. This allows us to pass additional information when we raise the exception.

Raising User-Defined Exceptions

Now that we have our custom exceptions, let's see how we can use them in our code. Raising an exception is like sounding an alarm when something goes wrong. Here's how you can do it:

def check_value(value):
    max_value = 100
    if value > max_value:
        raise ValueTooLargeError("Value is too large!", value)
    print(f"Value {value} is acceptable.")

# Let's try it out
try:
    check_value(150)
except ValueTooLargeError as error:
    print(f"Oops! {error.message} The value was {error.value}")

In this example, we're checking if a value is too large. If it is, we raise our ValueTooLargeError with a custom message and the actual value.

Handling User-Defined Exceptions

Handling user-defined exceptions is just like handling built-in exceptions. We use the trusty try-except block. Let's expand on our previous example:

def process_value(value):
    try:
        check_value(value)
    except ValueTooLargeError as error:
        print(f"Error: {error.message} The value {error.value} is not allowed.")
        # Here you could add code to handle the error, like asking for a new value
    else:
        print("Value processed successfully!")
    finally:
        print("Value checking complete.")

# Let's try it with different values
process_value(50)
process_value(200)

In this code, we're using a try-except block to handle our ValueTooLargeError. We also added an else clause that runs if no exception is raised, and a finally clause that always runs, regardless of whether an exception occurred or not.

Complete Example

Now, let's put it all together in a more complex example. Imagine we're creating a simple banking system:

class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        self.message = f"Insufficient funds. Balance: ${balance}, Attempted withdrawal: ${amount}"

class NegativeAmountError(Exception):
    def __init__(self, amount):
        self.amount = amount
        self.message = f"Cannot process negative amount: ${amount}"

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance

    def deposit(self, amount):
        if amount < 0:
            raise NegativeAmountError(amount)
        self.balance += amount
        print(f"Deposited ${amount}. New balance: ${self.balance}")

    def withdraw(self, amount):
        if amount < 0:
            raise NegativeAmountError(amount)
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        print(f"Withdrew ${amount}. New balance: ${self.balance}")

# Let's use our BankAccount class
account = BankAccount(100)

try:
    account.deposit(50)
    account.withdraw(30)
    account.withdraw(200)  # This should raise an InsufficientFundsError
except NegativeAmountError as error:
    print(f"Error: {error.message}")
except InsufficientFundsError as error:
    print(f"Error: {error.message}")
else:
    print("All transactions completed successfully.")
finally:
    print(f"Final balance: ${account.balance}")

In this example, we've created a BankAccount class with deposit and withdraw methods. We've also defined two custom exceptions: InsufficientFundsError and NegativeAmountError.

When we try to withdraw more money than we have in the account, it raises an InsufficientFundsError. If we try to deposit or withdraw a negative amount, it raises a NegativeAmountError.

This is a great example of how user-defined exceptions can make our code more readable and help us handle specific error cases in a clear and organized way.

Conclusion

Congratulations! You've just leveled up your Python skills by learning about user-defined exceptions. These custom exceptions are like your personal army of error-catchers, ready to jump into action when something unexpected happens in your code.

Remember, the key to mastering user-defined exceptions is practice. Try creating your own exceptions for different scenarios, and soon you'll be handling errors like a pro!

Here's a quick reference table of the methods we've covered:

Method Description
class CustomError(Exception): Creates a new exception class
raise CustomError() Raises a custom exception
try: Starts a try block
except CustomError as error: Catches a specific custom exception
else: Runs if no exception is raised
finally: Always runs, regardless of exceptions

Happy coding, and may your exceptions always be caught!

Credits: Image by storyset