This tutorial explains Exception Handling in Python using the Try Except block with the help of programming examples:
Two error types may cause a Python program to stop abruptly i.e. Syntax Errors, and Exceptions. In this tutorial, we will be discussing the second error type (Exceptions) under several important topics.
We will benefit a lot from handling exceptions in our application such as:
- Creating a robust application.
- Creating a clean and error-free code.
=> Visit Here For The Exclusive Python Training Tutorial Series
Table of Contents:
Python Try Except
One good news is that Python has a good number of built-in exceptions to catch errors in our code. Also, it gives us the opportunity to create custom exceptions when none of the built-in exceptions suits our needs.
What Is An Exception
So what is an exception in Python? Well, in simple terms, whenever the Python interpreter tries to execute invalid code, it raises an exception, and in the cases where such an exception is not handled, it disrupts the normal flow of the program’s instructions and prints a traceback.
Let’s create an invalid code and see how the Python interpreter will respond.
Open a Python shell and run the following code.
>>> 50/0
This is one of the most common errors in programming. The above code tries to divide the number 50 by 0 (zero). The Python interpreter sees this as an invalid operation and raises a ZeroDivisionError, disrupts the program, and prints a traceback.
We can see clearly that ZeroDivisionError is the exception that was raised. It’s indeed Python’s own way to tell us that it’s not cool to divide a number by zero. Though in other languages like JavaScript, this is not an error; and python strictly forbids this practice.
Also, it’s important to know that this is just an exception object and Python has many such objects in-built. Check out this Python official documentation to see all the Python Built-in Exceptions.
Understanding Traceback
Before we get into handling exceptions, I think it will help to understand what exactly will happen if exceptions are not handled and how Python does its best to inform us about our error.
Whenever Python encounters an error, it raises an exception. If this exception is not handled, then it produces some information called Traceback. So, what information does this traceback contain?
It contains:
- The error message that tells us what exception was raised and what happened before this exception was raised.
- The various line numbers of the code that caused this error. An error may be caused by a sequence of function calls called a call stack which we will discuss later here.
Though it’s a bit confusing, we promise that the next example will bring more light to our understanding.
Recall the traceback that was printed from dividing 50 by 0 above, we can see that the traceback contains the following information:
- File “<stdin>”: This tells us that this code was run from a console terminal.
- line 1: This tells us that the error occurred in this line number.
- ZeroDivisionError: division by zero: It tells us what exception was raised and what caused it.
Let’s try another example and perhaps see how a call stack looks like. Open an editor, enter the code below and save as tracebackExp.py
def stack1(numb): # 1 div = 0 # 2 stack2(numb, div) # 3 def stack2(numb, div): # 5 compute = numb/div # 6 print(compute) # 7 if __name__ == '__main__': # 9 numb = 5 # 10 stack1(numb) # 11
Open a terminal in the directory where this file is found and run.
python tracebackExp.py
You will see the following traceback:
The above traceback may seem confusing but really, it’s not. Pythonistas came up with the best way to read traceback, which is from the bottom up. So, let’s use this wisdom to try and understand what this traceback has to offer.
- At the bottom-most, we get the exception that was raised and why it was raised.
- Moving up, we get the file name tracebackExp.py where this error occurred, the computation that caused this error compute = numb/div, the function stack2, and the link number line 6 where this computation was performed.
- Moving up, we see that our stack2 function was called in the function stack1 in line number 3.
- Moving to the topmost, we see that the function stack1 was called in line number 11. <module> tells us that it is the file that is being executed.
Common Python Exceptions
The Python library defines an awful lot of built-in exceptions. You can check the Python Documentation or call the built-in local() function as below:
>>> dir(locals()['__builtins__'])
We will not try to address all these exceptions, but we shall see a few common exceptions that you will likely come across.
#1) TypeError
It is raised when an operation or function is applied to an object of an inappropriate type.
Example 1
Consider the below program. It takes in a dividend and divisor, then computes and prints the result of dividing the dividend by the divisor.
def compute_division(): dividend = int(input("Enter the dividend: ")) # cast string to int divisor = input("Enter the divisor: ") # no casting # Compute division result = dividend/divisor # print result print("The result of {}/{} is: {}".format(dividend, divisor, result)) if __name__ == '__main__': result = compute_division()
We request the value of the dividend and divisor from the user, but we forget to cast the divisor’s string value into an integer. So, we end up with the dividend’s type being integer(int) and the divisor’s type being string(str). We then get the TypeError as the division operator (/) doesn’t operate on strings.
It may interest you to know that unlike Python, Javascript has Type Coercion that basically converts one of the operand’s types to an equivalent value of the other operand’s type when the operands are of different types.
#2) ValueError
This is raised when an operation or function receives an argument that has the right type but an inappropriate value.
Example 2
Consider our program in Example 1 above.
If the user inputs an alphanumeric value for the dividend like ‘3a’, then our program will raise the ValueError exception. This is because, though Python int() method takes in any number or string and returns an integer object, the string value should not contain letters or any non-numeric value.
#3) AttributeError
This exception is raised while assigning or referencing an attribute that doesn’t exist.
Example 3
Consider the program below. It takes in a number and computes its square root using the Python math module
import math # import math library to gain access to its code def compute_square_root(number): # compute the square root using the math library result = math.sqr(number) return result if __name__ == '__main__': # get input to compute from user number = int(input("Compute Square root of: ")) # call function to compute square root
When a user enters a number, our program tries to use a function from the math module to compute its square root but just that here, we made an error. Instead of sqrt, we mistakenly typed sqr that doesn’t exist in the math module.
So, we were trying to reference an attribute sqr that doesn’t exist and led to the exception AttributeError being raised. Most of us make this kind of mistake a lot. So, you are not alone.
Handling Exceptions With Try Except
As a programmer, one thing that most of us will spend our time on is writing a robust code that is resilient. Code that doesn’t break due to some errors. In Python, we can achieve this by enclosing our statements inside a try–except statement.
Python Try-Except statement
The try-except statement has the following structure:
try: #your code goes here except """Specify exception type(s) here""": #handle exception here
Let’s enclose the code in tracebackExp.py inside a try-except statement.
def stack1(numb): # 1 div = 0 # 2 stack2(numb, div) # 3 def stack2(numb, div): # 5 try: # 6 compute = numb/div # 7 print(compute) # 8 except ZeroDivisionError as zde: # 9 print(zde) # 10 if __name__ == '__main__': # 12 numb = 5 # 13 stack1(numb) # 14 print("program continuous") # 15
Running this code will produce the output
This is how the try-except statement works. Python executes the code in the try block line 7-8. If no invalid code is found, then the code in the except block line 10 is skipped and the execution continues.
But, if an invalid code is found, then execution immediately stops in the try block and checks if the exception raised matches with the one we provided in the except statement line 9. If it matches, then the except block is executed and continues. If it doesn’t, then the program will interrupt.
The try-block usually contains the code that may raise an exception while the except-block catches and handles the exception.
Handling Multiple Exceptions With Except
We can handle multiple exceptions with either a single “except” or multiple “excepts”. It all depends on how you want to handle each exception.
#1) Handling Multiple Exceptions With A Single Except
try: #your code goes here except(Exception1[, Exception2[,...ExceptionN]]]): #handle exception here
This method is used when we suspect that our code may raise different exceptions and we want to take the same action in each case. So, if the Python interpreter finds a match, then the code written in the except block will execute.
Let’s consider the example Python code below
def get_fraction(value, idx): arr = [4,5,2,0] # a list of numbers idx_value = arr[idx] # if idx is > arr length, IndexError will be raised value/idx_value # if idx_value == 0, ZeroDivisionError will be raised if __name__ =='__main__': # set 'value' and 'idx' value = 54 idx = 3 # call function in a try-except statement. try: result = get_fraction(value, idx) print("Fraction is ", result) except (IndexError, ZeroDivisionError) as ex: print(ex)
We have two possible exceptions that could be raised here, ZeroDivisionError and IndexError. If any of these exceptions are raised, then the except block will be executed.
In the code above, idx=3, so idx_value becomes 0 and value/idx_value will raise ZeroDivisionError
#2) Handling Multiple Exceptions With Multiple Excepts
try: #your code goes here except Exception1: #handle exception1 here except Exception2: #handle exception2 here except ExceptionN: #handle exceptionN here
If we would rather want to handle each exception separately, then this is how you can do it.
Consider the example Python code below
def get_fraction(value, idx): arr = [4,5,2,0] # a list of numbers idx_value = arr[idx] # if idx is > arr length, IndexError will be raised value/idx_value # if idx_value == 0, ZeroDivisionError will be raised if __name__ =='__main__': # set 'value' and 'idx' value = 54 idx = 5 # call function in a try-excepts statement. try: result = get_fraction(value, idx) print("Fraction is ", result) except IndexError: print("idx of {} is out of range".format(idx)) except ZeroDivisionError: print("arr[{}] is 0. Hence, can't divide by zero".format(idx)) except Exception as ex: print(ex) print("Not sure what happened so not safe to continue, \ app will be interrupted") raise ex
We notice here that Exception was used in the last except statement. This is because the exception object Exception matches any exception. For this reason, it should always be last, since Python will stop checking other exception handlers once one matches.
In the code above, idx=5, hence arr[idx] will raise IndexError because idx is greater than the length of the list arr
Also, not sure what exception was raised by your application is never safe to continue execution. That’s why we have the type Exception to catch any unpredicted exceptions. Then, we inform the user and interrupt the application by raising the same exception.
Try Else Statement
This is an optional feature of exception handling and allows you to add code that you wish to run when no errors occurred. If an error occurs, this else-block won’t run.
Consider the example Python code bellow, open your editor and save the code as elseTry.py
def fraction_of_one(divisor): value = 1/divisor # if divisor is zero, ZeroDivisionError will be raised return value if __name__ == '__main__': while True: try: # Get input from the user. # if input is not a valid argument for int(), ValueError will be raised divisor = int(input("Enter a divisor: ")) # call our function to compute the fraction value = fraction_of_one(divisor) except (ValueError, ZeroDivisionError): print("Input can't be zero and should be a valid literal for int(). Please, try again!") else: print("Value: ", value) break
We get input from the user and use it to divide 1. We have two possible exceptions here, an invalid user input which will cause ValueError and a zero(0) which will cause ZeroDivisionError. Our except statement handles these errors.
Now, we want to print out the value of value. Our else-block makes sure it is printed only if our try block executes without an error. This is important because if an error occurs in our try-block, the value will be undefined. So, accessing it will raise another error.
Run the code above with Python elseTry.py
The output above shows that for the first input, we typed 0 and pressed ENTER. Since our divisor received 0, 1/divisor raised zeroDivisionError. Our second input was k which is invalid for int(), hence the exception ValueError is raised.
But our last input was 9 which is valid and as a result, we got the value of “value” printed as 0.1111111111111111
Try Finally Statement
This is also an optional feature of exception handling and will always run no matter what happens in the exception handlers.
That is:
- Whether or not an exception occurs
- Even if a ‘return’ is called in the other blocks.
- Even if the script is quit in the other blocks
So, if we have a code that we want to run in all situations, finally-block is our guy. This block is mostly used for clean-ups like closing files.
Consider the example Python code below
def readFile(file_path): try: openFile = open(file_path,'r') # Open a file as read-only print(openFile.readline()) # Read first line of file content except FileNotFoundError as ex: print(ex) finally: print("Cleaning...") openFile.close() if __name__ == '__main__': filePath = './text.txt' readFile(filePath)
This code tries to open and read the file text.txt in its current directory. If the file exists, then our program will print the first line of the file then our finally-block will run and close the file.
Say we have a file called text.txt in the directory where this program file is and contains Hello. If we run the program, we will have the output
This example was chosen intentionally because I wanted us to address a small problem that may occur when closing files in the finally-block.
If the file doesn’t exist, the exception FileNotFoundError will be raised and the variable openFile won’t be defined and won’t be a file object. Hence, trying to close it in the finally-block will raise an exception UnboundLocalError which is a subclass of NameError.
This basically says that we are trying to reference the variable openFile before it has been assigned.
A small trick here is to use exception handlers inside the finally-block.
def readFile(file_path): try: openFile = open(file_path,'r') # Open a file as read-only print(openFile.readline()) # Read first line of file content except FileNotFoundError as ex: print(ex) finally: try: print("Cleaning...") openFile.close() except: # catches all exceptions pass # Ignore this error because we don't care. if __name__ == '__main__': filePath = './text.txt' readFile(filePath)
If our try-block raises FileNotFoundError, then we will have the following output
Raise Exception
One good news about Python exceptions is that we can intentionally raise them. Exceptions are raised with the raise statement.
The raise statement has the following syntax:
raise [ExceptionName[(*args: Object)]]
Open a terminal and raise any exception object from the Python in-built Exceptions. For example, if we raise ZeroDivisionError:
>>> raise ZeroDivisionError("Can't divide by zero")
We shall get the traceback:
So, why is it important to raise exceptions?
- When working with custom exceptions.
- During sanity checks.
Custom Exception Classes
A custom exception is one which you create to handle errors that are specific to your need. The trick is, we define a class that derives from the object Exception, then we use the raise statement to raise our exception class.
Suppose we want to check the user input and make sure the input value is not negative(sanity check). Of course, we could raise the Python exception ValueError but we will like to customize the error by giving it a specific and self-explanatory name like InputIsNegativeError. But this exception is not a Python in-built Exception.
So first, we create our base class which will derive from Exception.
class CustomError(Exception): "Base class exception for all exceptions of this module" pass
Then we create our exception class that will inherit the base class and will handle our specific error.
class InputIsNegativeError(CustomError): """Raised when User enters a negative value""" pass
Let’s test this
try: value = int(input()) if value < 0: raise InputIsNegativeError # Raise exception if value is negative except InputIsNegativeError: # catch and handle exception print("Input value shouldn't be negative")
The above code request for user input, and check if it is negative. If true, it raises our custom exception InputIsNegativeError which is later caught in the except-statement.
Below is the complete code:
class CustomError(Exception): "Base class exception for all exceptions of this module" pass class InputIsNegativeError(CustomError): """Raised when User enters a negative value""" pass if __name__ == '__main__': try: value = int(input("Input a number: ")) if value < 0: raise InputIsNegativeError # Raise exception if value is negative except InputIsNegativeError: # catch and handle exception print("Input value shouldn't be negative")
If input value is a negative number like -1, then we will have the output:
Check out the Python doc for more details on Python custom exceptions.
Frequently Asked Questions
Q #1) How does Python handle an exception?
Answer: Python handles exceptions using the try-except statement. The code that can raise an exception is placed and executed in the try block while the except block holds the code that will handle the exceptions if any arises.
Q #2) What is raising an exception in Python?
Answer: Whenever the Python interpreter encounters an invalid code, it raises an exception, which is Python’s own way to tell us that something unexpected happened. We can as well intentionally raise exceptions using the raise statement.
Q #3) How does Python handle multiple exceptions?
Answer: Python handles multiple exceptions using either a single except block or multiple except blocks.
For a single block, the exceptions are passed as a tuple: except(Exception1, Exception2,..,ExceptionN) and Python checks for a match from right to left. In this case, the same action is taken for each exception.
Another way to catch all exceptions is to leave out the name of the exception after the except keyword.
except: # handle all exceptions here
The second way is to use an except block for each exception:
except Exception1: # code to handle Exception1 goes here except Exception2: # code to handle Exception2 goes here except ExceptionN: # code to handle ExceptionN goes here
This way, you can take separate actions for each Exception.
Q #4) Why is Exception handling important in Python?
Answer: The benefit of handling exceptions in Python is that we can create robust, clean and error-free applications. We won’t want our production code to crash due to some errors, so we handle the errors and keep our application up and running.
Q #5) How do you ignore an exception in Python?
Answer: To ignore an exception in Python, use the pass keyword in the except block. Let’s say we want to ignore the ValueError exception. We will do it this way:
except ValueError: pass
Unless you know what you are doing, it is bad practice to ignore exceptions. At least, inform the user about all potential errors.
Conclusion
In this tutorial, we covered: Python Exceptions, Traceback; how to handle exceptions with Try/Except/Else/Finally blocks, how to Raise Exceptions, and finally how to create our own Custom Exceptions.
Thanks for reading!
=> Visit Here To Learn Python From Scratch.