1

I'm looking for a sound and practical approach to storing different errors and/or exceptions (i.e. IOError, ValueError, <CustomError>, etc.) inside a class instance, but refraining from raise-ing them straight away. Instead I'd like to somehow communicate them to the script that import-ed and created the class instance in the first place, and raise the errors and exceptions there.

I'm not looking for a strategy on how to catch exceptions. For my custom exceptions, I use classes!

Is there a general approach for this?

Thanks.

St4rb0y
  • 317
  • 3
  • 5
  • 22

1 Answers1

1

You can define a "container/collector" class which contains your custom exceptions (Of course, without raising). This collector call in my example is the MyExceptions class.

You can define an unique exception as a class which is inherited from the related built-in exception. You can find the build-in exceptions and the exception structure (bottom of page) on this page: https://docs.python.org/2/library/exceptions.html

The basic syntax of an unique exception:

class MyException1(BaseException):
    """
    Exception is inherited from 'BaseException'.
    """
    def __str__(self):
        return "This is my Exception1. Inherited from 'BaseException'"

You can set the message of the exception with the __str__ or the __repr__ magic methods (I have used the __str__ in my example). It means, in the above example if you raise the MyException1 exception then the message in the traceback will be This is my Exception1. Inherited from 'BaseException'. You can read more about these magic methods on the following page: https://stackoverflow.com/a/2626364/11502612

my_exceptions.py:

class MyExceptions(object):
    """
    Collector class of Unique Exceptions
    """

    class MyException1(BaseException):
        """
        Exception is inherited from 'BaseException'.
        """
        def __str__(self):
            return "This is my Exception1. Inherited from 'BaseException'"

    class MyException2(Exception):
        """
        Exception is inherited from 'Exception'.
        """
        def __str__(self):
            return "This is my Exception2. Inherited from 'Exception'"

    class MyValueError(ValueError):
        """
        Exception is inherited from 'ValueError'.
        """
        def __str__(self):
            return "This is my MyValueError. Inherited from 'ValueError'"

    class AttributeError(BaseException):
        """
        Redefined 'AttributeError'. Inherited from 'ValueError'
        """
        def __str__(self):
            return "Redefined 'AttributeError'. Inherited from 'ValueError'"

As you can see above my exception collector class is the MyExceptions. This class contains other classes and actually these are the unique exceptions. I have written more type of exceptions. The unique exceptions are inherited from different build-in exceptions and the last one shows how you can re-defined a built-in exception (more-or-less).

Usage of these exceptions (test.py):

import my_exceptions


def raise_my_exception1():
    raise my_exceptions.MyExceptions.MyException1


def raise_my_exception2():
    raise my_exceptions.MyExceptions.MyException2


def raise_my_value_error():
    raise my_exceptions.MyExceptions.MyValueError


def raise_my_attribute_error():
    raise my_exceptions.MyExceptions.AttributeError


def raise_original_attribute_error():
    raise AttributeError("This is the original (built-in) AttributeError exception")


function_list = [
    raise_my_exception1,
    raise_my_exception2,
    raise_my_value_error,
    raise_my_attribute_error,
    raise_original_attribute_error,
]

for func in function_list:
    try:
        func()
    except BaseException as e:
        print(e)

As you can see in my above test.py file, I have imported the my_exceptions.py file as a Python module (import my_exceptions). The unique exceptions are raised in the functions. You can access the exceptions: <Module.CollectorClass.Exception>. The last raise_original_attribute_error function raises the built-in AttributeError with a custom message (It shows how you can separate your own AttributeError exception and the built-in AttributeError). The function_list list contains the references of the functions (With this solution I can call the function in a for loop and I don't need to call them one-by-one). In the for loop I defined a try/except structure and call the functions. I have used the BaseException built-in exception because it is the more wide exception in Python.

Output of the script:

>>> python2 test.py 
This is my Exception1. Inherited from 'BaseException'
This is my Exception2. Inherited from 'Exception'
This is my MyValueError. Inherited from 'ValueError'
Redefined 'AttributeError'. Inherited from 'ValueError'
This is the original (built-in) AttributeError exception

You can catch your own exceptions in a try/except (Separately from others):

import my_exceptions


def raise_my_exception1():
    raise my_exceptions.MyExceptions.MyValueError


def raise_other_exception():
    raise Exception("Unexpected exception")

for func in [raise_my_exception1, raise_other_exception]:
    try:
        func()
    except my_exceptions.MyExceptions.MyValueError as e:
        print(e)
        print("Handled exception. Do nothing!")
    except Exception as e:
        print(e)
        print("Not handled exception. Raise it!")
        raise e

Output:

>>> python2 test.py 
This is my MyValueError. Inherited from 'ValueError'
Handled exception. Do nothing!
Unexpected exception
Not handled exception. Raise it!
Traceback (most recent call last):
  File "test.py", line 20, in <module>
    raise e
Exception: Unexpected exception

And of course, you have many choice to use this own exception collector.

You can create an instance from the exception collector class:

import my_exceptions

exception_collector = my_exceptions.MyExceptions()


def raise_my_exception1():
    raise exception_collector.MyValueError

If you are a strict OOP developer, your can inherited your "functional" class from your exception class. In that case your can use the exceptions as "instance exceptions" (with self). Probably this way what you are looking for!

Example:

import my_exceptions


class FunctionalClass(my_exceptions.MyExceptions):
    def raise_my_exception1(self):
        raise self.MyValueError

    def raise_other_exception(self):
        raise Exception("Unexpected exception")


functional_instance = FunctionalClass()

for func in [functional_instance.raise_my_exception1, functional_instance.raise_other_exception]:
    try:
        func()
    except my_exceptions.MyExceptions.MyValueError as e:
        print(e)
        print("Handled exception. Do nothing!")
    except Exception as e:
        print(e)
        print("Not handled exception. Raise it!")
        raise e

Output:

>>> python2 test.py 
This is my MyValueError. Inherited from 'ValueError'
Handled exception. Do nothing!
Unexpected exception
Not handled exception. Raise it!
Traceback (most recent call last):
  File "test.py", line 23, in <module>
    raise e
Exception: Unexpected exception
milanbalazs
  • 4,811
  • 4
  • 23
  • 45
  • `try: except ValueError` will catch `ValueError` only or both ValueError and child exception? I know you can catch `MyValueError`. But is it possible to make some flexible exception? – Grzegorz Krug Aug 05 '20 at 23:29
  • 1
    Sure, if you inherited your own exception (`MyValueError`) from the `ValueError` built-in exception and you use the `ValueError` in the `except` then both of `MyValueError` and `ValueError` will be caught. You can imagine the exceptions as a tree with parents and child and if you check a parent then all included child will be caught. This fact is true for the built-in exception, you can see the exception hierarchy here: https://docs.python.org/2/library/exceptions.html#exception-hierarchy. You can make the most flexible own exception if you inherited it from the first parent (`BaseException`). – milanbalazs Aug 06 '20 at 05:32
  • 1
    Furthermore, here is a very nice and deep page about Python exception facts: https://julien.danjou.info/python-exceptions-guide/ – milanbalazs Aug 06 '20 at 05:33