How to rewrite the exception message using my customize exception process class?

Viewed 408

How to catch the exception which have the same name in MyException's method and run the corresponding method defined in MyException? Because i want to rewrite the error message with error code and error message.

class OrmError:
    # catch django orm process error
    pass

class SysError:
    def IndexError(self):
        print("index error, you need to ...")

    def SomeError1(self):
        print('something1')

    def SomeError2(self):
        print('something2')

class OtherError:
    pass

class MyException(OrmError, SysError, OtherError):
    # do something
    pass


try:
    lis = []
    a = lis[1]
except MyException:
    # how to catch the exception which have the same name in MyException's method 
    # and run the corresponding method defined in MyException, because i want to 
    # rewrite the error message
    pass
except Exception as e:
    # print uncaught error
    print(e)
3 Answers

You can try the following:

class MyException(Exception):
    def IndexError(self):
        print("index error, you need to ...")
    
    def SomeError1(self):
        print('something1')
    
    def SomeError2(self):
        print('something2')

try:
    lis = []
    a = lis[1]
except Exception as e:
    error_name = e.__class__.__name__

    if error_name in dir(MyException):
        # If error is handled in custom MyException
        ex = MyException()
        ex_function = getattr(ex, error_name)

        ex_function()
    else:
        # Otherwise, print default error message
        print(e)

e.__class__.__name__ gives you the type of exception raised, which you can then use to check if it is being defined/handled in your custom class.

Using classes in the way you are doing is an abuse of an otherwise effective tool. You are not trying to encapsulate anything here. Your idea of having a separate method for each exception you want to transform is fine, but you are missing a way to look up the exception type.

The simplest way to edit the message of a builtin exception is to modify the args attribute:

try:
    enumerate(1)
except Exception as e:
    print(e.args)
    e.args = (e.args[0].replace('not ', ''),)
    raise

The result is

("'int' object is not iterable",)
Traceback (most recent call last):

  File "<ipython-input-131-740297d24fb6>", line 1, in <module>
    try: enumerate(1)

TypeError: 'int' object is iterable

The next step is to provide a method of identifying exceptions and mapping their messages. You can do something similar to what a series of except blocks does: provide a list of the exceptions you want to catch, in the order you want to catch them, and supply a callable to transform each one. Order is important, so you can either use a list, or a an OrderedDict. In python 3.6+, regular dicts are ordered, but I will not rely on this functionality here.

You can transform the exceptions in a number of ways. The callables can accept an exception object or a string. They can return an exception object or a string, or nothing. I would recommend ingesting an returning nothing. This is because raising a specific exception object in an except clause, even if it is the same exception that triggered the clause, will restart the traceback. Compare the snippet above with the one below:

try:
    enumerate(1)
except Exception as e:
    print(e.args)
    e.args = (e.args[0].replace('not ', ''),)
    raise e

Notice that the source of the error is no longer the enumerate line:

("'int' object is not iterable",)
Traceback (most recent call last):

  File "<ipython-input-134-b1123bf70859>", line 6, in <module>
    raise e

  File "<ipython-input-134-b1123bf70859>", line 2, in <module>
    enumerate(1)

TypeError: 'int' object is iterable

So you can write something like this:

from collections import OrderedDict

def handleIndexError(e):
    e.args = ('You need to work on the size of your index',)

def handleMyCustomError(e):
    e.args = (f'Really, be more careful: some attribute {e.attr} needs to be better',)

def handleBadException(e):
    e.args = (f'This is a {type(e).__name__} error with message "{e.args[0]}"',)

handler_table = OrderedDict([
    (IndexError, handleIndexError),
    (MyCustomError, handleMyCustomError),
    ((MemoryError, OSError), handleBadException)])

def overwrite_exception(exception, table):
    for cls, handler in  table:
        if isinstance(exception, cls):
            return handler(exception)

try:
    lis = []
    a = lis[1]
except Exception as e:
    overwrite_exception(e, handler_table)
    raise

Notice that the OrderedDict does not give you much advantage over a list if you want to use isinstance functionality like the normal exceptions handlers do. If you want to match exact classes rather than using isinstance, you don't need ordering and a plain dict will do. In that case, you will have to list each type in a separate key. Normally, isinstance will accept a tuple, as in the last key of handler_table shown above.

The reason for having your handler accept the full exception object is that not all exceptions will use args for their str representation (or even have an args attribute. If you come across a type that uses a different mechanism to create the string representation, having the full exception object will allow you to adapt to it.

Also, don't forget about sys.exc_info, which can be used to glean information about the exception not available directly in the object itself.

I can achieve my goal in the following way, but i think this is not elegant. Is there a better way?

class OrmHandlelException(object):
    def IntegrityError(self):
        print('Integrity error')
        
# there may be many other type of exception 
# which i also want to classify with independent class        
class OtherException(object):
    pass      


class MyExceptionHandler(OrmHandlelException, OtherException):
    def __call__(self, error):
        error_handle_method = getattr(self, type(error).__name__, None)
        if error_handle_method is not None:
            error_handle_method()
        else:
            print(error)
    
my_exception_handler = MyExceptionHandler()    

try:
    print('do something here')
    lis = []
    a = lis[1]
except Exception as error:
    my_exception_handler(error)

Related