7.1 Creating Custom Exceptions
Sometimes the standard Python exceptions don't fully meet the needs of your application. In such cases, you can create your own exceptions, inheriting them from the base class Exception
or any other suitable exception class.
Basics of Creating Custom Exceptions
Creating a custom exception involves defining a new class that inherits from the base class Exception
. You can add your own methods and attributes to your exception class to provide additional information about the error.
Example of a simple custom exception creation
Step 1: Define the Custom Exception
class MyCustomError(Exception):
"""Class for custom exception."""
pass
Step 2: Use the Custom Exception
def check_value(value):
if value < 0:
raise MyCustomError("Value must not be less than zero")
try:
check_value(-1)
except MyCustomError as e:
print(f"A custom exception occurred: {e}")
It's pretty simple. The main point is that your exception should inherit from the Exception
class or one of its descendants.
7.2 Creating an Exception with Additional Attributes
You can add attributes and methods to your exception class to pass additional information about the occurred error.
Example:
class NegativeValueError( Exception ):
"""Class for custom exception when value is negative."""
def __init__(self, value, message = "Value must not be less than zero"):
self.value = value
self.message = message
super().__init__(self.message)
def __str__(self):
return f'{self.message}: {self.value}'
Using the Exception with Additional Attributes
def check_value(value):
if value < 0:
raise NegativeValueError(value)
try:
check_value(-1)
except NegativeValueError as e:
print(f"A custom exception occurred: {e}")
Our exception is a class that is inherited from the Exception
class. So you can do everything with it as with any other class: add fields, methods, constructor parameters, etc.
All for your convenience, and the convenience of those programmers who will be catching your exceptions.
7.3 Creating a Hierarchy of Custom Exceptions
For more complex applications, it's useful to create hierarchies of custom exceptions. This allows you to group related exceptions and simplify their handling.
Example:
class ApplicationError(Exception):
"""Base class for all application exceptions."""
pass
class NegativeValueError(ApplicationError):
"""Class for custom exception when value is negative."""
def __init__(self, value, message="Value must not be less than zero"):
self.value = value
self.message = message
super().__init__(self.message)
def __str__(self):
return f'{self.message}: {self.value}'
class ValueTooLargeError(ApplicationError):
"""Class for custom exception when value is too large."""
def __init__(self, value, message="Value is too large"):
self.value = value
self.message = message
super().__init__(self.message)
def __str__(self):
return f'{self.message}: {self.value}'
Using the Hierarchy of Custom Exceptions
def check_value(value):
if value < 0:
raise NegativeValueError(value)
elif value > 100:
raise ValueTooLargeError(value)
try:
check_value(150)
except NegativeValueError as e:
print(f"An exception occurred: {e}")
except ValueTooLargeError as e:
print(f"An exception occurred: {e}")
except ApplicationError as e:
print(f"General application exception: {e}")
7.4 Order of Exception Handling
When handling exceptions, especially from one hierarchy, it's important to specify their correct order. Even though the code inside except
blocks never executes simultaneously, it's all about the fact that the base exception class is able to catch exceptions of all descendant classes.
For example, the code:
def check_value(value):
if value < 0:
raise NegativeValueError(value)
elif value > 100:
raise ValueTooLargeError(value)
try:
check_value(150)
except ApplicationError as e: # will catch exceptions of type ApplicationError and all its descendants
print(f"General application exception: {e}")
In the except
block, exceptions of type ApplicationError
and all its descendant classes will be caught.
And since all exceptions are descendants of the Exception
class, such code will catch all exceptions:
def check_value(value):
if value < 0:
raise NegativeValueError(value)
elif value > 100:
raise ValueTooLargeError(value)
try:
check_value(150)
except Exception as e:
print(f"Catching all exceptions: {e}")
except ApplicationError as e: # This code will never execute
print(f"General application exception: {e}")
Catching exceptions in the except ApplicationError
block will never happen because the except Exception
block will catch all exceptions of type Exception
and its descendants.
Solution
Therefore, it is customary to catch exceptions in the reverse order of inheritance: the closer the class is to the Exception
class, the lower it is.
Example:
def check_value(value):
if value < 0:
raise NegativeValueError(value)
elif value > 100:
raise ValueTooLargeError(value)
try:
check_value(150)
except NegativeValueError as e:
print(f"An exception occurred: {e}")
except ApplicationError as e:
print(f"General application exception: {e}")
except Exception as e:
print(f"Catching all exceptions: {e}")
In this example, the except
blocks are arranged in an order that matches their inheritance hierarchy: first, more specific exceptions like NegativeValueError
are caught, then more general ones like ApplicationError
. This allows for proper exception handling and avoids situations where a more general handler catches an exception before more specialized except
blocks can handle it.
GO TO FULL VERSION