# What is an exception? # something the computer tells us it doesn't know # what/how we want it to do # example: # 10 divided by 0 or # 10 divided by "abc" # Exception is an object print("---- example 1 ----") denom = int(input("Enter a denominator: ")) print(10/denom) # This will run as long as the input demon # is non-zero number. If denom is zero or non-numeric, # program crashes. # We want a way to ensure the program won't run # if denom is zero or non-numeric # One possible solution is to use conditional statement # to check before performing the division # note: for simplicity, let's consider only numeric denom print("\n---- example 2 ----") denom = int(input("Enter a denominator: ")) if denom == 0: print("Denominator must not be zero") denom = int(input("Enter a denominator: ")) print(10/denom) # Example run: # denom = 0, print statement, get new denom # denom = 2, print 10/2 --> no problem # what if the 2nd denom = 0 # an exception is raised # One possition to this is to use a loop print("\n---- example 3 ----") denom = int(input("Enter a denominator: ")) while denom == 0: print("Denominator must not be zero") denom = int(input("Enter a denominator: ")) print(10/denom) # This only works if we have a human user to read the message. # However, in practice, sometimes we don't know # who performs the division. # Imagine, if the above code is in a function. def divide(denom): if denom == 0: print("Denominator must not be zero") denom = "what should be here?" # ??? print(10 / denom) # We don't know if the input is given from an actual user (through a keyboard) # or from file, the web/internet, or function. # We can't simply ask for input again. # One possible solution is to, instead, only compute when # denom is not zero # def divide(denom): # if denom != 0: # print(10 / denom) # This works as we prevent an exception. # However, imagine sometimes we may have a program # that involves multiple functions, # some of which may not prevent exceptions. # To handle the exception (consider the divide function), # we would let an exception to propagate. def divide(denom): print(10/denom) def math_driver(): num = int(input("Enter a denominator: ")) try: divide(num) except: while num == 0: print("Denominator must not be zero") num = int(input("Enter a denominator: ")) divide(num) print("\n---- example 4 ----") math_driver() # Assume denom = 0, # an exception occurs at line print(10/denom) # Once the exception occurs, the code (this function) # stops immediately. Then, the control of execution # goes back to where divide() was called. # Whoever call divide() should know how denom is given. # If it doesn't, it then propagated the exception # up the chain to the upper caller. # Therefore, we need to let an exception to propagate # That is, an exception becomes visible. # So that we can handle the exception properly. ########################### # Consider another example why we need to let exception to propagate. # Assume another_num is obtained from a file (e.g., read from data file) def divide(denom): print(10/denom) def read_from_file(): # read a number from file # return the number return 0 # hardcoded value here to demonstrate exception def math_driver(): num = int(input("Enter a denominator: ")) try: divide(num) except: while num == 0: print("Denominator must not be zero") num = int(input("Enter a denominator: ")) divide(num) another_num = read_from_file() # divide(another_num) # exception can happen here # to protect and and handle an exception that might occur # use a try-except block try: divide(another_num) except: print("Try again: ") divide(another_num) # while another_num == 0: # print("Try again: ") # another_num = read_from_file() # divide(another_num) print("\n---- example 5 ----") math_driver() ########################### # Exceptions try to propagate the issue # to the piece of code that can best handle it and recover; # using an if-else doesn’t have this ability to propagate ###########################