“The robustness of your code is not dependent on language but how you safeguard your code under abnormal circumstances. After all, it is all about survival of the fittest”

Exceptions are used by developers across the globe to describe runtime errors/ faults which might cause the program to terminate or behave abnormally. They often serve as a means of communication by allowing your program to tell you what, why and how something went wrong. Usually exceptions can occur because of user errors,developer assumptions/neglect, lack of testing or due to abnormal situations such as internet access problem, permission issue etc.

For example :

num1 = int(input('Enter num1: '))

num2 = int(input('Enter num2: '))



print(num1 / num2)

Assume, this is the code snippet from a calculator application. This code seems perfectly fine from a syntactical point of view. But if user tries to input a real number or num2 as 0?

Looks like my application terminates abruptly in both the cases showing some scary error message to the user which is highly undesirable. Hence, to prevent failing of our applications under any circumstances it is important that we should identity our probable runtime errors and take measures to handle them.

Understanding Exceptions in Python :

All exceptions in python, if not handled, terminate your program and generate a similar error message :

While the second line tells us where the error was caused, the last line indicates what operation caused the termination and the type of exception that occurred.

In Python, all exceptions must be instances of classes derived from BaseException.

Python provides us with a range of built-in exceptions for commonly encountered scenarios. Some of them are described below :

exception TypeError : Raised when operation/function is applied to an object of incorrect type. Example situation which may lead to this :

exception ValueError: Raised when function gets argument of correct type but improper value. Example situation which may lead to this :

exception AttributeError: Raised when an attribute reference/assignment fails.Example situation which may lead to this :

exception NameError: Raised when identifier not found in global/local namespace.Example situation which may lead to this :

exception KeyError: Raised when a specified key is not found in a dictionary.Example situation which may lead to this :

exception ImportError: Raised when an import statement has troubles to load a module.Example situation which may lead to this :

exception IndexError: Raised when index of a sequence is out of range. Example situation which may lead to this :

exception PermissionError: Raised when trying to run an operation without access rights.Example situation which may lead to this :

exception IsADirectoryError: Raised when file operation is requested on a directory.Example situation which may lead to this :

exception FileNotFoundError: Raised when a file/directory is requested but doesn’t exist.Occurs when we are trying to append/read such a file.Example situation which may lead to this :

exception FileExistsError: Raised when trying to create an existing file/directory.Example situation which may lead to this :

These built in Exception classes or the Exception class should be subclassed to create new user defined Exceptions instead of directly subclassing BaseException.

Handling Exceptions in Python :

With a thorough understanding of the different runtime errors that might cause our programs to behave abnormally, we are now good to go on how to tackle such problems. In Python,we handle exceptions by implementing a try and except block :

Syntax :

try:

#suspected code block which may raise the exception

except Exception:

#code which handles the raised exception

(Please note it is possible to have multiple except blocks for one try block handling the different exceptions which might occur.)

Our program starts executing from the try block. If any exception is encountered, an object of type Exception is generated which is caught in the except block and is taken care of. In case, no exceptions occur, after the completion of the try block, the except block is skipped and the next statement, if any, gets executed or the program gracefully terminates.

Let us try to modify our previous code snippet from the calculator application and make it robust.

num1 = ''

num2 = ''

try:

num1 = int(input('Enter integer num1: '))

num2 = int(input('Enter integer num2: '))

print(num1 / num2)

except TypeError:

print('Expected value is an integer. Try running the program again')

except ZeroDivisionError:

print('n/0 operation isn\' t permitted.Could you try entering another input for num2?')

num2 = int(input('Enter integer num2: '))

print(num1 / num2)

Output :

However, our task as a developer doesn’t stop here by preventing two probable exceptions that might occur. There can be scenarios wherein the user might interrupt KeyBoardInterrupt, run out of memory or well encounter any problem. How can we prevent the very little chances of those scenarios? Because you as a developer cannot be available 24/7 to help your code tackle such situations, it is best that along with the probable exceptions you dedicate one except block to Exception and also log the user activity and the errors into a file which you can later access.

For maintaining any log, the user entry and exit time in the application ought to be noted whether or not we encounter an exception. This is to be done in the finally block.

Syntax :

try:

#suspected code block which may raise the exception

except Exception:

#code which handles the raised exception

finally:

#code that must execute whether exception encountered/not

Modifying the previous code snippet for one last time :

import logging #open the logging file

logging.basicConfig(filename='logger.txt', level=logging.DEBUG,

format='%(asctime)s : %(name)s : %(levelname)s : %(message)s')



#log login activity

logging.info('root logged in')



num1 = ''

num2 = ''

try:

num1 = int(input('Enter integer num1: '))

num2 = int(input('Enter integer num2: '))



print(num1 / num2)

#log task activity

logging.info('printed num1/num2')



except TypeError as e:

print('Expected value is an integer. Try running the program again')

#log error activity for wrong type

logging.error(str(e))



except ZeroDivisionError:

print('n/0 operation isn\'t permitted.Could you try entering another input for num2?')

#log error activity for zero division

logging.error('num2 was intially 0 and hence another input was taken.')

num2 = int(input('Enter integer num2: '))

print(num1 / num2)

#log task activity

logging.info('printed num1/num2')



except Exception as e:

logging.error(str(e))



finally:

#log logout activity

logging.info('root logged out')

That’s all in store for today.

Thank you for your read. If you enjoyed this article, don’t forget to applaud and share it with your friends ! Also, feel free to reach out in comments if you have any suggestions or feedback for this article.