Inheriting from the Base Exception Class

๐Ÿท๏ธ Error Handling and Exceptions / Custom Exceptions

๐ŸŽฏ Context Introduction

Python provides a rich set of built-in exceptions like ValueError, TypeError, and KeyError. However, as you build more complex systems, you will encounter situations where none of these built-in exceptions accurately describe the problem. This is where custom exceptions come in. By inheriting from Python's base Exception class, you can create your own exception types that make your code more readable, maintainable, and easier to debug.


โš™๏ธ Why Create Custom Exceptions?

  • Clarity: A custom exception like ConfigFileNotFoundError tells you exactly what went wrong, unlike a generic FileNotFoundError.
  • Control: You can catch specific exceptions from your application without accidentally catching unrelated errors from third-party libraries.
  • Organization: Custom exceptions help group related errors together, making your error handling logic cleaner.
  • Extensibility: You can add custom attributes or methods to your exceptions to carry additional context about the error.

๐Ÿ› ๏ธ The Base Exception Class

Every custom exception in Python should inherit from the Exception class (or one of its subclasses). The Exception class itself inherits from BaseException, but you should always inherit from Exception unless you have a very specific reason not to.

Key points about the Exception class: - It provides the basic structure for all standard exceptions. - It accepts an optional message argument that describes the error. - It has a built-in args attribute that stores the arguments passed to the constructor. - It supports string representation through the str method.


๐Ÿ“Š Creating a Simple Custom Exception

To create a custom exception, you define a new class that inherits from Exception. Here is the basic pattern:

  • Define a class with a name ending in Error (by convention).
  • Make it inherit from Exception.
  • Optionally, add a custom init method to accept additional parameters.

Example structure: - Class name: ConfigurationError - Inherits from: Exception - Purpose: Raised when a configuration file is missing or malformed

The simplest custom exception requires only the pass statement inside the class body. Python's Exception class handles everything else.


๐Ÿ•ต๏ธ Adding Custom Attributes

Custom exceptions become more powerful when you add attributes that carry additional context. For example, you might want to include the name of a missing file or the invalid value that caused the error.

To add custom attributes: - Override the init method in your exception class. - Call the parent init method with a descriptive message. - Store additional data as instance attributes.

Example attributes for a ValidationError: - field_name: The name of the field that failed validation - invalid_value: The value that was rejected - expected_type: What type was expected instead

These attributes can then be accessed in your exception handling code to provide detailed error messages or to make decisions about how to recover.


๐Ÿ“‹ Comparison: Built-in vs Custom Exceptions

Feature Built-in Exceptions Custom Exceptions
Purpose General-purpose errors Application-specific errors
Readability May require comments to explain context Self-documenting through class name
Specificity Broad categories (e.g., ValueError) Narrow and precise (e.g., InvalidPortError)
Extensibility Fixed attributes Can carry custom data
Maintenance Cannot be modified Fully under your control
Reusability Works across all Python code Specific to your project

๐Ÿงฉ Best Practices for Custom Exceptions

  • Inherit from Exception: Always inherit from Exception, not BaseException. The BaseException class is reserved for system-exiting exceptions like KeyboardInterrupt and SystemExit.
  • Use descriptive names: Name your exception classes clearly, ending with Error (e.g., DatabaseConnectionError, InvalidInputError).
  • Keep it simple: Start with a basic exception and add complexity only when needed. A custom exception with just a message is often sufficient.
  • Document your exceptions: Include a docstring in your exception class explaining when it should be raised.
  • Create a hierarchy: For complex applications, create a base custom exception for your project and have more specific exceptions inherit from it.

๐ŸŒŸ Practical Example: A Configuration Error Hierarchy

Consider a system that reads configuration files. You might create the following exception hierarchy:

  • ConfigError (inherits from Exception): Base exception for all configuration-related errors
  • ConfigFileNotFoundError (inherits from ConfigError): Raised when a config file does not exist
  • ConfigParseError (inherits from ConfigError): Raised when a config file has invalid syntax
  • ConfigValidationError (inherits from ConfigError): Raised when a config value fails validation

This hierarchy allows you to catch all configuration errors with a single except ConfigError block, or handle specific cases individually.


๐Ÿ”„ Raising and Catching Custom Exceptions

Custom exceptions are raised and caught exactly like built-in exceptions:

  • Use the raise keyword followed by an instance of your custom exception.
  • Pass a descriptive message as an argument to the constructor.
  • Use try/except blocks to catch your custom exceptions.
  • You can catch the base custom exception to handle all related errors, or catch specific subclasses for targeted handling.

When catching custom exceptions, remember that the order of except clauses matters. More specific exceptions should be listed before more general ones.


โœ… Summary

  • Custom exceptions inherit from the Exception class and allow you to create application-specific error types.
  • They improve code readability by making error conditions explicit and self-documenting.
  • You can add custom attributes to carry additional context about what went wrong.
  • A well-designed exception hierarchy helps organize error handling in complex systems.
  • Custom exceptions are raised and caught using the same syntax as built-in exceptions.

By creating your own exception classes, you transform vague error messages into precise, actionable information that makes debugging and maintenance significantly easier.


Inheriting from the base Exception class lets engineers create their own custom error types that behave like built-in Python exceptions.


๐Ÿงฑ Example 1: Simplest custom exception with pass

This example shows the minimum code needed to create a custom exception class.

class MyCustomError(Exception):
    pass

raise MyCustomError("Something went wrong")

๐Ÿ“ค Output: MyCustomError: Something went wrong


๐Ÿ”ง Example 2: Custom exception with a custom message

This example shows how to pass and store a custom message inside the exception.

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

raise ValueTooLargeError(150, "Value exceeds the allowed limit")

๐Ÿ“ค Output: ValueTooLargeError: Value exceeds the allowed limit


๐Ÿงช Example 3: Using a custom exception in a try-except block

This example shows how to catch and handle a custom exception like any built-in exception.

class NegativeNumberError(Exception):
    pass

def check_positive(number):
    if number < 0:
        raise NegativeNumberError("Negative numbers are not allowed")
    return number

try:
    result = check_positive(-5)
except NegativeNumberError as error:
    print(f"Caught error: {error}")

๐Ÿ“ค Output: Caught error: Negative numbers are not allowed


๐Ÿ“Š Example 4: Custom exception with additional attributes

This example shows how to store extra data inside the exception for debugging.

class InvalidAgeError(Exception):
    def __init__(self, age, min_age, max_age):
        self.age = age
        self.min_age = min_age
        self.max_age = max_age
        message = f"Age {age} is not between {min_age} and {max_age}"
        super().__init__(message)

try:
    raise InvalidAgeError(200, 0, 120)
except InvalidAgeError as error:
    print(f"Problem: {error}")
    print(f"Supplied age: {error.age}")
    print(f"Allowed range: {error.min_age} to {error.max_age}")

๐Ÿ“ค Output: Problem: Age 200 is not between 0 and 120
๐Ÿ“ค Output: Supplied age: 200
๐Ÿ“ค Output: Allowed range: 0 to 120


๐Ÿ›ก๏ธ Example 5: Custom exception hierarchy for a validation system

This example shows how to create a family of related custom exceptions for different error types.

class ValidationError(Exception):
    pass

class EmptyFieldError(ValidationError):
    def __init__(self, field_name):
        self.field_name = field_name
        super().__init__(f"{field_name} cannot be empty")

class InvalidFormatError(ValidationError):
    def __init__(self, field_name, expected_format):
        self.field_name = field_name
        self.expected_format = expected_format
        super().__init__(f"{field_name} must be in format: {expected_format}")

def validate_email(email):
    if not email:
        raise EmptyFieldError("Email")
    if "@" not in email:
        raise InvalidFormatError("Email", "[email protected]")
    return True

try:
    validate_email("bad-email")
except EmptyFieldError as error:
    print(f"Empty field: {error}")
except InvalidFormatError as error:
    print(f"Format issue: {error}")
except ValidationError as error:
    print(f"General validation error: {error}")

๐Ÿ“ค Output: Format issue: Email must be in format: [email protected]


๐Ÿ“‹ Comparison Table

Feature Built-in Exception Custom Exception
Definition Provided by Python Created by engineers
Inheritance Inherits from BaseException Inherits from Exception
Custom attributes Limited Engineers can add any data
Reusability General purpose Specific to application needs
Readability Generic names Descriptive names for the domain

๐ŸŽฏ Context Introduction

Python provides a rich set of built-in exceptions like ValueError, TypeError, and KeyError. However, as you build more complex systems, you will encounter situations where none of these built-in exceptions accurately describe the problem. This is where custom exceptions come in. By inheriting from Python's base Exception class, you can create your own exception types that make your code more readable, maintainable, and easier to debug.


โš™๏ธ Why Create Custom Exceptions?

  • Clarity: A custom exception like ConfigFileNotFoundError tells you exactly what went wrong, unlike a generic FileNotFoundError.
  • Control: You can catch specific exceptions from your application without accidentally catching unrelated errors from third-party libraries.
  • Organization: Custom exceptions help group related errors together, making your error handling logic cleaner.
  • Extensibility: You can add custom attributes or methods to your exceptions to carry additional context about the error.

๐Ÿ› ๏ธ The Base Exception Class

Every custom exception in Python should inherit from the Exception class (or one of its subclasses). The Exception class itself inherits from BaseException, but you should always inherit from Exception unless you have a very specific reason not to.

Key points about the Exception class: - It provides the basic structure for all standard exceptions. - It accepts an optional message argument that describes the error. - It has a built-in args attribute that stores the arguments passed to the constructor. - It supports string representation through the str method.


๐Ÿ“Š Creating a Simple Custom Exception

To create a custom exception, you define a new class that inherits from Exception. Here is the basic pattern:

  • Define a class with a name ending in Error (by convention).
  • Make it inherit from Exception.
  • Optionally, add a custom init method to accept additional parameters.

Example structure: - Class name: ConfigurationError - Inherits from: Exception - Purpose: Raised when a configuration file is missing or malformed

The simplest custom exception requires only the pass statement inside the class body. Python's Exception class handles everything else.


๐Ÿ•ต๏ธ Adding Custom Attributes

Custom exceptions become more powerful when you add attributes that carry additional context. For example, you might want to include the name of a missing file or the invalid value that caused the error.

To add custom attributes: - Override the init method in your exception class. - Call the parent init method with a descriptive message. - Store additional data as instance attributes.

Example attributes for a ValidationError: - field_name: The name of the field that failed validation - invalid_value: The value that was rejected - expected_type: What type was expected instead

These attributes can then be accessed in your exception handling code to provide detailed error messages or to make decisions about how to recover.


๐Ÿ“‹ Comparison: Built-in vs Custom Exceptions

Feature Built-in Exceptions Custom Exceptions
Purpose General-purpose errors Application-specific errors
Readability May require comments to explain context Self-documenting through class name
Specificity Broad categories (e.g., ValueError) Narrow and precise (e.g., InvalidPortError)
Extensibility Fixed attributes Can carry custom data
Maintenance Cannot be modified Fully under your control
Reusability Works across all Python code Specific to your project

๐Ÿงฉ Best Practices for Custom Exceptions

  • Inherit from Exception: Always inherit from Exception, not BaseException. The BaseException class is reserved for system-exiting exceptions like KeyboardInterrupt and SystemExit.
  • Use descriptive names: Name your exception classes clearly, ending with Error (e.g., DatabaseConnectionError, InvalidInputError).
  • Keep it simple: Start with a basic exception and add complexity only when needed. A custom exception with just a message is often sufficient.
  • Document your exceptions: Include a docstring in your exception class explaining when it should be raised.
  • Create a hierarchy: For complex applications, create a base custom exception for your project and have more specific exceptions inherit from it.

๐ŸŒŸ Practical Example: A Configuration Error Hierarchy

Consider a system that reads configuration files. You might create the following exception hierarchy:

  • ConfigError (inherits from Exception): Base exception for all configuration-related errors
  • ConfigFileNotFoundError (inherits from ConfigError): Raised when a config file does not exist
  • ConfigParseError (inherits from ConfigError): Raised when a config file has invalid syntax
  • ConfigValidationError (inherits from ConfigError): Raised when a config value fails validation

This hierarchy allows you to catch all configuration errors with a single except ConfigError block, or handle specific cases individually.


๐Ÿ”„ Raising and Catching Custom Exceptions

Custom exceptions are raised and caught exactly like built-in exceptions:

  • Use the raise keyword followed by an instance of your custom exception.
  • Pass a descriptive message as an argument to the constructor.
  • Use try/except blocks to catch your custom exceptions.
  • You can catch the base custom exception to handle all related errors, or catch specific subclasses for targeted handling.

When catching custom exceptions, remember that the order of except clauses matters. More specific exceptions should be listed before more general ones.


โœ… Summary

  • Custom exceptions inherit from the Exception class and allow you to create application-specific error types.
  • They improve code readability by making error conditions explicit and self-documenting.
  • You can add custom attributes to carry additional context about what went wrong.
  • A well-designed exception hierarchy helps organize error handling in complex systems.
  • Custom exceptions are raised and caught using the same syntax as built-in exceptions.

By creating your own exception classes, you transform vague error messages into precise, actionable information that makes debugging and maintenance significantly easier.

Interactive Views

You are currently in ๐Ÿ“š All-in-One mode. Use the tabs at the top to switch to ๐Ÿ“– Theory Only or ๐Ÿ’ป Code Only views.

Inheriting from the base Exception class lets engineers create their own custom error types that behave like built-in Python exceptions.


๐Ÿงฑ Example 1: Simplest custom exception with pass

This example shows the minimum code needed to create a custom exception class.

class MyCustomError(Exception):
    pass

raise MyCustomError("Something went wrong")

๐Ÿ“ค Output: MyCustomError: Something went wrong


๐Ÿ”ง Example 2: Custom exception with a custom message

This example shows how to pass and store a custom message inside the exception.

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

raise ValueTooLargeError(150, "Value exceeds the allowed limit")

๐Ÿ“ค Output: ValueTooLargeError: Value exceeds the allowed limit


๐Ÿงช Example 3: Using a custom exception in a try-except block

This example shows how to catch and handle a custom exception like any built-in exception.

class NegativeNumberError(Exception):
    pass

def check_positive(number):
    if number < 0:
        raise NegativeNumberError("Negative numbers are not allowed")
    return number

try:
    result = check_positive(-5)
except NegativeNumberError as error:
    print(f"Caught error: {error}")

๐Ÿ“ค Output: Caught error: Negative numbers are not allowed


๐Ÿ“Š Example 4: Custom exception with additional attributes

This example shows how to store extra data inside the exception for debugging.

class InvalidAgeError(Exception):
    def __init__(self, age, min_age, max_age):
        self.age = age
        self.min_age = min_age
        self.max_age = max_age
        message = f"Age {age} is not between {min_age} and {max_age}"
        super().__init__(message)

try:
    raise InvalidAgeError(200, 0, 120)
except InvalidAgeError as error:
    print(f"Problem: {error}")
    print(f"Supplied age: {error.age}")
    print(f"Allowed range: {error.min_age} to {error.max_age}")

๐Ÿ“ค Output: Problem: Age 200 is not between 0 and 120
๐Ÿ“ค Output: Supplied age: 200
๐Ÿ“ค Output: Allowed range: 0 to 120


๐Ÿ›ก๏ธ Example 5: Custom exception hierarchy for a validation system

This example shows how to create a family of related custom exceptions for different error types.

class ValidationError(Exception):
    pass

class EmptyFieldError(ValidationError):
    def __init__(self, field_name):
        self.field_name = field_name
        super().__init__(f"{field_name} cannot be empty")

class InvalidFormatError(ValidationError):
    def __init__(self, field_name, expected_format):
        self.field_name = field_name
        self.expected_format = expected_format
        super().__init__(f"{field_name} must be in format: {expected_format}")

def validate_email(email):
    if not email:
        raise EmptyFieldError("Email")
    if "@" not in email:
        raise InvalidFormatError("Email", "[email protected]")
    return True

try:
    validate_email("bad-email")
except EmptyFieldError as error:
    print(f"Empty field: {error}")
except InvalidFormatError as error:
    print(f"Format issue: {error}")
except ValidationError as error:
    print(f"General validation error: {error}")

๐Ÿ“ค Output: Format issue: Email must be in format: [email protected]


๐Ÿ“‹ Comparison Table

Feature Built-in Exception Custom Exception
Definition Provided by Python Created by engineers
Inheritance Inherits from BaseException Inherits from Exception
Custom attributes Limited Engineers can add any data
Reusability General purpose Specific to application needs
Readability Generic names Descriptive names for the domain