John
John

Reputation: 81

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

Upvotes: 4

Views: 1500

Answers (3)

Vishvajit Pathak
Vishvajit Pathak

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

Dawid Sawa
Dawid Sawa

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

clemens
clemens

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

Related Questions