Search This Blog

Custom Exception Classes in Python

 

Custom Exception Classes in Python

In Python, you can create custom exceptions by defining your own exception classes. Custom exceptions allow you to raise and handle errors that are specific to your application’s domain, providing more control over the program flow and better error handling.

Custom exceptions are typically used when you want to signal a particular condition in your application that requires special handling.


1. Defining a Custom Exception Class

To define a custom exception class in Python, you need to:

  1. Subclass the built-in Exception class (or any other appropriate exception class).
  2. Optionally, override the __init__ method to add custom behavior or data.
  3. Optionally, override the __str__ method to customize the error message when the exception is raised.

Here’s how you can create a basic custom exception:

class CustomError(Exception):
    pass

In this example:

  • CustomError inherits from the built-in Exception class.
  • This class doesn’t add any extra functionality, but it can be raised like any other exception.

2. Raising a Custom Exception

You can raise a custom exception in your code using the raise keyword. This is typically done when you want to signal that something unusual or specific to your application has occurred.

class InvalidAgeError(Exception):
    pass

def validate_age(age):
    if age < 18:
        raise InvalidAgeError("Age must be 18 or older.")
    print(f"Age {age} is valid.")

try:
    validate_age(15)
except InvalidAgeError as e:
    print(f"Error: {e}")

In this example:

  • The InvalidAgeError custom exception is raised if the age is less than 18.
  • The except block catches the exception and prints the error message.

3. Customizing the Exception Class

You can extend the functionality of a custom exception by overriding the __init__ method to accept additional parameters. This allows you to pass extra information when the exception is raised.

Example: Customizing with __init__ and __str__

class InvalidWithdrawalError(Exception):
    def __init__(self, balance, withdrawal_amount):
        self.balance = balance
        self.withdrawal_amount = withdrawal_amount

    def __str__(self):
        return f"Withdrawal of {self.withdrawal_amount} exceeds available balance of {self.balance}."

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

try:
    balance = 1000
    new_balance = withdraw(balance, 1200)
except InvalidWithdrawalError as e:
    print(f"Error: {e}")

In this example:

  • The InvalidWithdrawalError class takes two parameters: balance and withdrawal_amount.
  • The __str__ method is overridden to return a custom error message that includes both the balance and the withdrawal amount.

4. Handling Multiple Custom Exceptions

You can raise and handle different custom exceptions in your code, just like with built-in exceptions. By defining multiple custom exception classes, you can create more granular error handling.

Example: Handling Multiple Custom Exceptions

class InsufficientFundsError(Exception):
    pass

class UnauthorizedAccessError(Exception):
    pass

def perform_transaction(amount, balance, user_permission):
    if amount > balance:
        raise InsufficientFundsError("Not enough funds to complete the transaction.")
    if not user_permission:
        raise UnauthorizedAccessError("User is not authorized to perform the transaction.")
    return balance - amount

try:
    balance = 500
    user_permission = False
    new_balance = perform_transaction(100, balance, user_permission)
except InsufficientFundsError as e:
    print(f"Error: {e}")
except UnauthorizedAccessError as e:
    print(f"Error: {e}")

In this example:

  • If there are insufficient funds, the InsufficientFundsError is raised.
  • If the user lacks permission to perform the transaction, the UnauthorizedAccessError is raised.
  • Each exception is caught and handled with a specific message.

5. Chaining Exceptions

Sometimes, you may want to raise a new exception while retaining the original exception information. You can do this by chaining exceptions using the from keyword.

Example: Chaining Exceptions

class DatabaseError(Exception):
    pass

class ConnectionError(DatabaseError):
    pass

def connect_to_database():
    try:
        # Simulate a connection failure
        raise ConnectionError("Unable to connect to the database.")
    except ConnectionError as e:
        raise DatabaseError("Database connection failed.") from e

try:
    connect_to_database()
except DatabaseError as e:
    print(f"Error: {e}")
    if e.__cause__:
        print(f"Caused by: {e.__cause__}")

In this example:

  • The ConnectionError is raised inside the connect_to_database() function.
  • A new DatabaseError is raised, chaining the original ConnectionError using the from keyword.
  • The cause of the exception can be accessed through e.__cause__.

6. Using Custom Exceptions for Validation

Custom exceptions are often useful when you want to validate specific conditions in your application. For example, you might want to ensure that data is in a certain format or within a particular range.

Example: Custom Validation Exception

class InvalidInputError(Exception):
    pass

def validate_input(user_input):
    if not user_input.isnumeric():
        raise InvalidInputError("Input must be a number.")
    return int(user_input)

try:
    user_input = input("Enter a number: ")
    result = validate_input(user_input)
except InvalidInputError as e:
    print(f"Error: {e}")
else:
    print(f"Valid input! The number is {result}.")

In this example:

  • The InvalidInputError custom exception is raised if the user input is not numeric.
  • The exception is caught, and an appropriate error message is displayed.

7. Best Practices for Custom Exceptions

  • Use descriptive names: Name your custom exceptions in a way that clearly describes the error condition, such as InvalidInputError, ConnectionError, or InsufficientFundsError.
  • Avoid overuse: Only create custom exceptions when they make sense for your application’s domain. For most cases, built-in exceptions like ValueError, TypeError, and FileNotFoundError are sufficient.
  • Provide helpful error messages: Custom exceptions should provide meaningful error messages that help the user (or developer) understand what went wrong and how to fix it.
  • Chain exceptions when necessary: If your custom exception is caused by another exception, use exception chaining to retain the original error information.

Summary of Custom Exceptions

  1. Defining Custom Exceptions: You can define a custom exception by subclassing the Exception class.
  2. Raising Custom Exceptions: Use raise to raise custom exceptions when specific conditions occur.
  3. Overriding __str__: You can customize the error message for your exception by overriding the __str__ method.
  4. Multiple Custom Exceptions: You can define and handle multiple custom exceptions for different error conditions.
  5. Chaining Exceptions: You can raise a new exception while preserving the original exception using the from keyword.
  6. Validation and Best Practices: Custom exceptions are particularly useful for input validation and handling specific errors in your application.

Custom exceptions allow you to make your Python programs more robust and easier to maintain by providing more meaningful error handling tailored to your needs.

Popular Posts