# Topic covered
* Exception Handling
* Syntax Errors
* Runtime Errors
* Custom Exception
* List Of General Use Exceptions
* Python’s Exception Hierarchy

3.1 Exception

An unwanted and unexpected event that disturbs normal flow of program is called exception.

In any programming language there are 2 types of errors are possible.

  1. Syntax Errors
  2. Runtime Errors

3.2 Syntax Errors

The errors which occurs because of invalid syntax are called syntax errors.

x=10
if x==10
print("Hello")

# SyntaxError: invalid syntax

Programmer is responsible to correct these syntax errors. Once all syntax errors are corrected then only program execution will be started.

3.3 Runtime Errors

AKA exceptions. While executing the program if something goes wrong because of end user input or programming logic or memory problems etc then we will get Runtime Errors.

print(10/0)
# ZeroDivisionError: division by zero

print(10/"ten")
# TypeError: unsupported operand type(s) for /: 'int' and 'str

3.4 Exception Handling

It is highly recommended to handle exceptions. The main objective of exception handling is Graceful Termination of the program

To handle exception and proper execution of program we put Risky code inside try-except block

try-except

try:
    Risky Code
except XYZ:
    Handling code/Alternative Code

Default except block - handles all exception

try:
    Risky Code
except:
    Handling code/Alternative Code

try with multiple except blocks

try:
    Risky Code
except ZeroDivisionError:
    Handling code/Alternative Code
except FileNotFoundError:
    Handling code/Alternative Code

Single except block that can handle multiple exceptions

try:
    Risky Code
except (Exception1,Exception2,exception3,..):
    Handling code/Alternative Code

# except (Exception1,Exception2,exception3,..) as msg

try-except-else

You can use Python’s else statement to instruct a program to execute a certain block of code only in the absence of exceptions

try-except-else

num = 1
try:
    print(10/num)
except Exception as e:
    print('Exception: ', str(e))
else:
    print('Else')

try-except-else-finally

Imagine that you always had to implement some sort of action to clean up after executing your code. Python enables you to do so using the finally.

finally block is executed always irrespective of whether exception raised or not raised and whether exception handled or not handled.

try-except-else-finally

num = 0
try:
    print(10/num)
except Exception as e:
    print('Exception: ', str(e))
finally:
    print('Finally')
num = 1
try:
    print(10/num)
except Exception as e:
    print('Exception: ', str(e))
else:
    print('Else')
finally:
    print('Finally')

There is only one situation where finally block won’t be executed ie whenever we are using os._exit(0) function.

3.5 Custom Exception

We can define custom exception by using "raise" keyword

age = -1
if age < 1:
    raise RuntimeError("Wrong age")
else:
    print("Done")
class NegativeAgeException(Exception):
    def __init__(self,arg):
        self.msg=arg

age = -1
if age < 1:
    raise NegativeAgeException("Age can't be Negative")
else:
    print("Valid Age")

3.6 List Of General Use Exceptions

try :
    a = 10 
    10/0
except ZeroDivisionError as e :
    print(e)
# division by zero
try :
    int("sudh")
except (ValueError , TypeError) as e :
    print(e)
# invalid literal for int() with base 10: 'sudh'
# Not good practice
# Shoulld use always a specific exception

try :
    int("sudh")
except  :
    print("this will catch an error")
# this will catch an error


try :
    with open("test.txt" , 'r') as f :
        f.read()
except Exception as e :
    print("test " , e)
except FileNotFoundError as e :
    print("this is my file not found type error " , e)

# test  [Errno 2] No such file or directory: 'test.txt'
# last `except block` will never get executed
try :
    import sudh
except ImportError as e : 
    print(e)
# No module named 'sudh'
try :
    d = {1: [3,4,5,6] , "key" :"sudh"}
    d["key10"]
except KeyError as e : 
    print(e)
# 'key10'
try :
    "sudh".test()
except AttributeError as e :
    print(e)
# 'str' object has no attribute 'test'
try :
    l = [1,2,3,3]
    l[10]
except IndexError as e :
    print(e)
# list index out of range
try :
    123 + "sudh"
except TypeError as e :
    print(e)
# unsupported operand type(s) for +: 'int' and 'str'
try :
    with open("test.txt" , 'r') as f :
        f.read()
except FileNotFoundError as e :
    print(e)
# [Errno 2] No such file or directory: 'test.txt'

3.7 Python’s Exception Hierarchy

python-exception-hierarchy

Every Exception in Python is a class.

All exception classes are child classes of BaseException i.e. every exception class extends BaseException either directly or indirectly. Hence, BaseException acts as root for Python Exception Hierarchy.

# Print Python Exception Hierarchy
def treeClass(cls, ind=0):
    # print name of the class
    print('-' * ind, cls.__name__)

    # iterating through subclasses
    for i in cls.__subclasses__():
        treeClass(i, ind + 3)

print("Hierarchy for Built-in exceptions is : ")
treeClass(BaseException)