Exception handling
Programs throw error when the Python interpreter encounters a statement which is illegal and cannot be executed. This could be a programmer error or user input error or system error. Such a condition is called throwing an exception. For e.g,, if you try to import a package cheesecake which does not exist then you receive ModuleNotFoundError on the console. We have earlier come across IndexError in String Operations chapter when we try to access an element position which is out of bounds for a given string.
It could also be the user input error. Even though you have displayed the message that the age should be a number, there will be some user who will input a letter instead of a number and if you are converting the input string to a number, an exception will be thrown.
Exceptions should be handled gracefully and at the instand when it is thrown otherwise your code is not considered efficient. For e.g., when an error is thrown during processing of a large amount of data, then the program crashes and all the work that the program would have completed until that error point is lost if not handled gracefully. This is not desirable when processing massive amount of data which takes significant amount of time to process and thus leads to inefficiencies in computing.
Instead of crashing the program, it is desirable to collect all the issues and complete the given execution and after all the data is fully processed, you can look into the error situations and give a fix for all the errors, the next time it is run. We use Exception handling using try and except key words to achieve this task.
In the code below, we fix the erroneous import by using try block. Put all the code which could potentially throw an error inside the try block and when an exception is thrown, the program of execution jumps to except block which will handle the exception meaningfully. After handling the exception, the program of execution continues further on with the next statements after except without crashing the program.
try:
import cheesecake
except ImportError:
print("Such a module does not exist")
print("program continues after the exception is excepted")
Note however, that it is not always a good thing to move on when an exception happens during execution. If the cheesecake module is absolutely required by you to process all your data then you should crash the program. However, supposing if you are processing a million records of users input data, you may come across a few invalid entries in the middle of otherwise good entries. Then in such situations you can collect all the invalid records separately and complete the processing of the rest of the million records. Then when the program finishes you can look into invalid entries and take action appropriately for all of those.
An optional finally block can be added to the try catch block. Finally block will be executed irrespective of weather an exception is thrown on not. Typically, finally blocks contain code for releasing resources like closing open file handles, releasing database connections etc., which were established in the try block. Then upon either completing the try block successfully or upon an exception situation, irrespective of the situation, the database connection or files should be closed. Such type of code is added in finally block. If you do not release the resources in the finally block and an exception happens during execution, then the program crashes without releasing the resources in a graceful way.
Note that file and database operations will be covered in the next book. Although these two concepts are very important, we rarely use pure Python to open files or databases. Instead we use pandas module for these operations and hence we cover them in NumPy and Pandas book.
Below is a program which handles invalid user inputs elegantly by informing the user what exactly went wrong and asks user to input the correct values instead of crashing the program.
print("This program calculates the rate of interest for your deposit. \nPlease input your deposit and the total interest earned/year\nand I will calculate the rate of interest\n")
print("To quit the program enter 'quit' in small case\n\n")
# program in an infinite loop till user enters quit
while True:
try: # try statement encloses statements which could throw an exception
user_input1 = input("Enter Deposit Amount:")
if user_input1 == 'quit':
print("Good bye! Hope you had fun getting your interest rates!")
break
user_input2 = input('Enter total interest earned:')
deposit_amount = float(user_input1)
interest_earned = float(user_input2)
interest_rate = (interest_earned / deposit_amount) * 100
# round function rounds the result to 2 decimal places
print("Rate of interest:"+ str(round(interest_rate, 2)))
except: # Catch all exceptions if nothing specified.
print("You entered an invalid integer. Please type a valid value.")
continue;
finally:
# Typically resources are released in this block
print("This will be executed no matter what")
The program starts by letting the user know that he has to input deposit amount and total the interest earned. The user has to input quit to exit the program. Once the user inputs valid numerical values, the program calculates the interest. If the user inputs illegal values, the program is catch in except block where the user is informed of what went wrong. The except block in the above program catches all exceptions. The two types of erroneous input the user can give are:
- alpha characters for number
- deposit value 0 - exception thrown due to division by 0
You can refine the program to add more clarity on the exception raised with the below program:
print("""This program calculates the rate of interest for your deposit.
Please input your deposit and the total interest earned/year
and I will calculate the rate of interest\n""")
print("To quit the program enter 'quit' in small case\n\n")
while True:
try: # try statement encloses statements which could throw an exception
user_input1 = input("Enter Deposit Amount:")
if user_input1 == 'quit':
print("Good bye! Hope you had fun getting your interest rates!")
break
deposit_amount = float(user_input1)
user_input2 = input('Enter total interest earned:')
interest_earned = float(user_input2)
interest_rate = (interest_earned / deposit_amount) * 100
# round function rounds the result to 2 decimal places
print("Rate of interest:"+ str(round(interest_rate , 2)))
except ValueError: # catch ValueError. This happens when the user inputs alpha instead of numeric value
print("You entered an invalid integer. Please type a valid value")
continue
except ZeroDivisionError: # This happens when the deposit amount is 0
print("You entered a deposit value of 0... ha ha I still did not crash! Input a valid deposit amount please!")
continue
except: # User input issues are already caught so crash the program
raise
finally:
print("finally block will be executed no matter what") # typically resources are released in this block
In the first two except blocks we mention the Error name that could occur. In the last except block, we catch all other exceptions that may occur. However when it reaches the third block we have exhausted all possible error conditions due to the user input. Then, in that case when it reaches this block, it has to be some system fault and in that case we have to crash. For that reason we have used the raise statement which rethrows the catch exception and crashes the program. The last except block can be completely removed if we are only rethrowing the exception as it will crash anyways if that block is not there. This is just added here to explain raise statement.