Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception handling in python using class

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

like image 214
John Avatar asked Aug 31 '25 02:08

John


2 Answers

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.

like image 105
Dawid Sawa Avatar answered Sep 02 '25 17:09

Dawid Sawa


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__)
like image 37
clemens Avatar answered Sep 02 '25 16:09

clemens