Search code examples
python-3.xexceptionraise

Raising custom Exception in python


I'm new to python exception handling and was experimenting with the code I've written below and have a bunch of doubts:

class CustomException(Exception):
    def __init__(self,msg):
        self.msg=msg
        print( 'custom exception occurred')

class Test:
    def __init__(self):
        self.func1()

    def func1(self):
        try:
            print(40/0)
        except Exception as e:
            print("entered exception block")
            raise CustomException("Error message")
obj=Test()

If I comment out raise CustomException("Error message") line, the program is handling the code by printing "entered exception block" in the except block and not giving the Traceback stack but Why is that if there are both print and raise statement getting the following output:

entered exception block
custom exception occurred
Traceback (most recent call last):
  File "c:\Users\inavis\Desktop\customException1.py", line 12, in func1
    print(40/0)
          ~~^~
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\Users\inavis\Desktop\customException1.py", line 16, in <module>
    obj=Test()
        ^^^^^^
  File "c:\Users\inavis\Desktop\customException1.py", line 8, in __init__
    self.func1()
  File "c:\Users\inavis\Desktop\customException1.py", line 15, in func1
    raise CustomException("Error message")
CustomException: Error message

I get that custom exception is present because I'm raising it but why the ZeroDivisionError? Since the print and raise statement are present in this particular except block, does it not mean the exception ZeroDivisionError should be handled?


Solution

  • This is by design. The raise statement has a from clause that lets you do exception chaining. This is done automatically if you raise within an exception handler. From the docs The raise statement

    A similar mechanism works implicitly if a new exception is raised when an exception is already being handled. An exception may be handled when an except or finally clause, or a with statement, is used. The previous exception is then attached as the new exception’s context attribute

    Because you raise within an exception block, Python will attach the previous exception to the new exception's __context__ attribute and that context may be displayed to the user. It is not something you can catch or need to handle although code could process the exception chain if there is some reason why it would be useful.

    This can be annoying and can keep objects from the original exception from being cleaned up and deleted. You can suppress automatic chaining by explicitly chaining None

    class CustomException(Exception):
        def __init__(self,msg):
            self.msg=msg
            print( 'custom exception occurred')
    
    class Test:
        def __init__(self):
            self.func1()
    
        def func1(self):
            try:
                print(40/0)
            except Exception as e:
                print("entered exception block")
                raise CustomException("Error message") from None
    obj=Test()
    

    Output

    entered exception block
    custom exception occurred
    Traceback (most recent call last):
      File "/home/td/tmp/e/o.py", line 16, in <module>
        obj=Test()
      File "/home/td/tmp/e/o.py", line 8, in __init__
        self.func1()
      File "/home/td/tmp/e/o.py", line 15, in func1
        raise CustomException("Error message") from None
    __main__.CustomException: Error message
    

    Python does not automatically re-raise an exception. If your code knows how to handle the error, it can take appropriate action and the code will resume after the try/except/finally clause (running the code in the finally if it's there).

    And you don't need to catch all exceptions. In your example func1 could have skipped the try/except if it didn't have anything useful to do. The exception would have gone to func1's caller and etc... up to the top of the program. If nobody catches it, the program terminates.

    The purpose of your example code is to translate an exception into one that makes more sense in your domain. This is common. Instead of expecting a caller to figure out something vague like ValueError, it can make sense to translate that into something in the problem domain.