Reputation: 81
I have doubt in python exception. Below code is taken from python document and I am confuse at one point. If any one can help, will be thankful. Here this codes gives output as:
B C D
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
If I change except part of code like below code: output will be :
B B B
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except B:
print("B")
except C:
print("C")
except D:
print("D")
When I run this code without try block as shown below:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
raise cls()
Here output is:
Traceback (most recent call last):
File "C:/Users/885710/Documents/PY/ErrorHandling.py", line 12, in <module>
raise cls()
B
Similary for below code:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [C,B, D]:
raise cls()
Output is this
Traceback (most recent call last):
File "C:/Users/885710/Documents/PY/ErrorHandling.py", line 12, in <module>
raise cls()
C
I confused because, if I run this code separately then it gives output as B or C or D then why in my second code snippet it is giving output as
B
B
B
even though except is define for all 3 Class B, C, D
Upvotes: 4
Views: 1500
Reputation: 3711
Let's first understand the class hierarchy involved in this code sample
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
B -> Base class
C -> inherits from B
D -> inherits from C and C -> inherits from B
thus D -> inherits from B and C
A try statement may have more than one except clause for different exceptions. But at most one except clause will be executed
Unless you are catching the raised the exception then base class is given the priority to be caught. i.e when exception classes are inherited then the priority in the except
is given to the BASE class (in your code it's class B
).
Now the first case :
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
Iteration 1
:
element : B
First control checks the except D
as it's not a base class nor the matching class to the raised one then control will move to except C
and at last it will execute except B
, thus prints B
.
Iteration 2
:
element : C
First control checks the except D
as it's not a base class nor the matching class to the raised one then control will move to except C
and will execute except C
, thus prints C
Iteration 3
:
element : D
First control checks the except D
as it's a matching class then control will
execute except D
, thus prints D
Now consider Second case :
for cls in [B, C, D]:
try:
raise cls()
except B:
print("B")
except C:
print("C")
except D:
print("D")
Here B
is the base class for classes C
and D
so when you write except B
at the top of except stack then the control does not go to subsequent except C
and except D
. So it prints output as B
for each iteration of for loop.
Upvotes: 1
Reputation: 2194
Python's documentation says:
A class in an except clause is compatible with an exception if it is the same class or a base class
Therefore the given code:
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
Can be simplified into the following:
for cls in [B, C, D]:
foo = cls()
if isinstance(foo, D):
print("D")
elif isinstance(foo, C):
print("C")
elif isinstance(foo, B):
print("B")
Then your modification would be turned into:
for cls in [B, C, D]:
foo = cls()
if isinstance(foo, B):
print("B")
elif isinstance(foo, C):
print("C")
elif isinstance(foo, D):
print("D")
So regardless whether foo
is instance of B
, C
or D
it will fulfil the first case, because isinstance
yields True
for instances of a subclass too.
Upvotes: 2
Reputation: 17711
Since B
is the super class of C
and D
your second version will always use the first except
block for B
. Because the Python runtime will search for a matching except
block from top to bottom. An except
block is matching if the exception is an instance of the class in the except
block. If you throw an instance C
for instance this block will match because C()
is (also) an instance of B
.
As a rule of thumb the except
statements must decrease from the most specific condition to the most general condition, e. g.:
try:
throw ...
except D: # the most specific class
print("D")
except C: # is more specific than B but less than D
print("C")
except B: # the most general class in your hierarchy
print("B")
except BaseException as e: the most general exception class
print(e.__class__.__name__)
Upvotes: 1