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?
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.