Introduction
Software applications don’t run perfectly all the time. Despite intensive debugging and multiple testing levels, applications still fail. Bad data, broken network connectivity, corrupted databases, memory pressures, and unexpected user inputs can all prevent an application from performing normally. When such an event occurs, and the app is unable to continue its normal flow, this is known as an exception. And it's your application's job—and your job as a coder—to catch and handle these exceptions gracefully so that your app keeps working.
Install the Python SDK to identify and fix exceptions
What Are Python Exceptions?
Exceptions in Python applications can happen for many of the reasons stated above and more; and if they aren't handled well, these exceptions can cause the program to crash, causing data loss, or worse, corrupted data. As a Python developer, you need to think about possible exception situations and include error handling in your code.
Fortunately, Python comes with a robust error handling framework. Using structured exception handling and a set of pre-defined exceptions, Python programs can determine the error type at run time and act accordingly. These can include actions like taking an alternate path, using default values, or prompting for correct input.
This article will show you how to raise exceptions in your Python code and how to address exceptions.
 
Difference Between Python Syntax Errors and Python Exceptions
Before diving in, it's important to understand the two types of unwanted conditions in Python programming—syntax error and exception.
The syntax error exception occurs when the code does not conform to Python keywords, naming style, or programming structure. The interpreter sees the invalid syntax during its parsing phase and raises a SyntaxError exception. The program stops and fails at the point where the syntax error happened. That’s why syntax errors are exceptions that can’t be handled.
Here’s an example code block with a syntax error (note the absence of a colon after the “if” condition in parentheses):
a = 10
b = 20
if (a < b)
print('a is less than b')
c = 30
print(c)
The interpreter picks up the error and points out the line number. Note how it doesn’t proceed after the syntax error:
File "test.py", line 4
if (a < b)
^
SyntaxError: invalid syntax
Process finished with exit code 1
On the other hand, an exception happens when the code has no syntax error but encounters other error situations. These conditions can be addressed within the code—either in the current function or in the calling stack. In this sense, exceptions are not fatal. A Python program can continue to run if it gracefully handles the exception.
Here is an example of a Python code that doesn’t have any syntax errors. It’s trying to run an arithmetic operation on two string variables:
a = 'foo'
b = 'bar'
print(a % b)
The exception raised is TypeError:
Traceback (most recent call last):
File "test.py", line 4, in
print(a % b)
TypeError: not all arguments converted during string formatting
Process finished with exit code 1
Python throws the TypeError exception when there are wrong data types. Similar to TypeError, there are several built-in exceptions like:
- ModuleNotFoundError
- ImportError
- MemoryError
- OSError
- SystemError
- ... And so on
You can refer to the Python documentation for a full list of exceptions.
 
How to Throw an Exception in Python
Sometimes you want Python to throw a custom exception for error handling. You can do this by checking a condition and raising the exception, if the condition is True. The raised exception typically warns the user or the calling application.
You use the “raise” keyword to throw a Python exception manually. You can also add a message to describe the exception
Here is a simple example: Say you want the user to enter a date. The date has to be either today or in the future. If the user enters a past date, the program should raise an exception:
Python Throw Exception Example
from datetime import datetime
current_date = datetime.now()
print("Current date is: " + current_date.strftime('%Y-%m-%d'))
dateinput = input("Enter date in yyyy-mm-dd format: ")
# We are not checking for the date input format here
date_provided = datetime.strptime(dateinput, '%Y-%m-%d')
print("Date provided is: " + date_provided.strftime('%Y-%m-%d'))
if date_provided.date() < current_date.date():
raise Exception("Date provided can't be in the past")
To test the code, we enter a date older than the current date. The “if” condition evaluates to true and raises the exception:
Current date is: 2021-01-24
Enter date in yyyy-mm-dd format: 2021-01-22
Date provided is: 2021-01-22
Traceback (most recent call last):
File "test.py", line 13, in
raise Exception("Date provided can't be in the past")
Exception: Date provided can't be in the past
Process finished with exit code 1
Instead of raising a generic exception, we can specify a type for it, too:
if (date_provided.date() < current_date.date()):
raise ValueError("Date provided can't be in the past")
With a similar input as before, Python will now throw this exception:
raise ValueError("Date provided can't be in the past")
ValueError: Date provided can't be in the past
 
Using AssertionError in Python Exception Throwing
Another way to raise an exception is to use assertion. With assertion, you assert a condition to be true before running a statement. If the condition evaluates to true, the statement runs, and the control continues to the next statement. However, if the condition evaluates to false, the program throws an AssertionError. The diagram below shows the logical flow:
Using AssertionError, we can rewrite the code snippet in the last section like this:
from datetime import datetime
current_date = datetime.now()
print("Current date is: " + current_date.strftime('%Y-%m-%d'))
dateinput = input("Enter date in yyyy-mm-dd format: ")
# We are not checking for the date input format here
date_provided = datetime.strptime(dateinput, '%Y-%m-%d')
print("Date provided is: " + date_provided.strftime('%Y-%m-%d'))
assert(date_provided.date() >= current_date.date()), "Date provided can't be in the past"
Note how we removed the “if” condition and are now asserting that the date provided is greater than or equal to the current date. When inputting an older date, the AssertionError displays:
Current date is: 2021-01-24
Enter date in yyyy-mm-dd format: 2021-01-23
Traceback (most recent call last):
Date provided is: 2021-01-23
File "test.py", line 12, in
assert(date_provided.date() >= current_date.date()), "Date provided can't be in the past"
AssertionError: Date provided can't be in the past
Process finished with exit code 1
 
Catching Python Exceptions with Try-Except
Now that you understand how to throw exceptions in Python manually, it’s time to see how to handle those exceptions. Most modern programming languages use a construct called “try-catch” for exception handling. With Python, its basic form is “try-except”. The try-except block looks like this:
Python Try Catch Exception Example
...
try:
<--program code-->
except:
<--exception handling code-->
<--program code-->
...
Here, the program flow enters the “try” block. If there is an exception, the control jumps to the code in the “except” block. The error handling code you put in the “except” block depends on the type of error you think the code in the “try” block may encounter.
Here’s an example of Python’s “try-except” (often mistakenly referred to as “try-catch-exception”). Let’s say we want our code to run only if the Python version is 3. Using a simple assertion in the code looks like this:
import sys
assert (sys.version_info[0] == 3), "Python version must be 3"
If the of Python version is not 3, the error message looks like this:
Traceback (most recent call last):
File "test.py", line 2, in
assert (sys.version_info[0] == 3), "Python version must be 3"
AssertionError: Python version must be 3
Process finished with exit code 1
Instead of letting the program fail with an unhandled exception and showing an ugly error message, we could use a try-except block for a graceful exit.
import sys
try:
assert (sys.version_info[0] == 3), "Python version must be 3"
except Exception as e:
print(e)
rollbar.report_exc_info()
Now, the error message is much cleaner:
Python version must be 3
The “except” keyword in the construct also accepts the type of error you want to handle. To illustrate this, let’s go back to our date script from the last section. In that script, we assumed the user will enter a date in “YYYY-MM-DD” format. However, as a developer, you should cater to any type of erroneous data instead of depending on the user. For example, some exception scenarios can be:
- No date entered (blank)
- Text entered
- Number entered
- Time entered
- Special characters entered
- Different date format entered (such as “dd/mm/yyyy”)
To address all these conditions, we can rewrite the code block as shown below. This will catch any ValueError exception raised when meeting any of the above conditions:
from datetime import datetime
current_date = datetime.now()
print("Current date is: " + current_date.strftime('%Y-%m-%d'))
dateinput = input("Enter date in yyyy-mm-dd format: ")
try:
date_provided = datetime.strptime(dateinput, '%Y-%m-%d')
except ValueError as e:
print(e)
rollbar.report_exc_info()
exit()
print("Date provided is: " + date_provided.strftime('%Y-%m-%d'))
assert(date_provided.date() >= current_date.date()), "Date provided can't be in the past"
In this case, the program will exit gracefully if the date isn’t correctly formatted.
You can include multiple “except” blocks for the “try” block to trap different types of exceptions. Each “except” block will address a specific type of error:
...
try:
<--program code-->
except <--Exception Type 1-->:
<--exception handling code-->
except <--Exception Type 2-->:
<--exception handling code-->
except <--Exception Type 3-->:
<--exception handling code-->
...
Here’s a very simple example:
num0 = 10
try:
num1 = input("Enter 1st number:")
num2 = input("Enter 2nd number:")
result = (int(num1) * int(num2))/(num0 * int(num2))
except ValueError as ve:
print(ve)
rollbar.report_exc_info()
exit()
except ZeroDivisionError as zde:
print(zde)
rollbar.report_exc_info()
exit()
except TypeError as te:
print(te)
rollbar.report_exc_info()
exit()
except:
print('Unexpected Error!')
rollbar.report_exc_info()
exit()
print(result)
We can test this program with different values:
Enter 1st number:1
Enter 2nd number:0
division by zero
Enter 1st number:
Enter 2nd number:6
invalid literal for int() with base 10: ''
Enter 1st number:12.99
Enter 2nd number:33
invalid literal for int() with base 10: '12.99'
 
Catching Python Exceptions with Try-Except-Else
The next element in the Python “try-except” construct is “else”:
Python Try Except Else Example
...
try:
<--program code-->
except <--Exception Type 1-->:
<--exception handling code-->
except <--Exception Type 2-->:
<--exception handling code-->
except <--Exception Type 3-->:
<--exception handling code-->
...
else:
<--program code to run if "try" block doesn't encounter any error-->
...
The “else” block runs if there are no exceptions raised from the “try” block. Looking at the code structure above, you can see the “else” in Python exception handling works almost the same as an “if-else” construct
To show how “else” works, we can slightly modify the arithmetic script’s code from the last section. If you look at the script, you will see we calculated the variable value of “result” using an arithmetic expression. Instead of putting this in the “try” block, you can move it to an “else” block. That way, the exception blocks any error raised while converting the input values to integers. And if there are no exceptions, the “else” block will perform the actual calculation:
num0 = 10
try:
num1 = int(input("Enter 1st number:"))
num2 = int(input("Enter 2nd number:"))
except ValueError as ve:
print(ve)
rollbar.report_exc_info()
exit()
except ZeroDivisionError as zde:
print(zde)
rollbar.report_exc_info()
exit()
except TypeError as te:
print(te)
rollbar.report_exc_info()
exit()
except:
print('Unexpected Error!')
rollbar.report_exc_info()
exit()
else:
result = (num1 * num2)/(num0 * num2)
print(result)
Using simple integers as input values runs the “else” block code:
Enter 1st number:2
Enter 2nd number:3
0.2
And using erroneous data like empty values, decimal numbers, or strings cause the corresponding “except” block to run and skip the “else” block:
Enter 1st number:s
invalid literal for int() with base 10: 's'
Enter 1st number:20
Enter 2nd number:
invalid literal for int() with base 10: ''
Enter 1st number:5
Enter 2nd number:6.4
invalid literal for int() with base 10: '6.4'
However, there’s still a possibility of unhandled exceptions when the “else” block runs:
Enter 1st number:1
Enter 2nd number:0
Traceback (most recent call last):
File "test.py", line 19, in
result = (num1 * num2)/(num0 * num2)
ZeroDivisionError: division by zero
As you can see, both inputs are integers (1 and 0), and the “try” block successfully converts them to integers. When the “else” block runs, we see the exception.
So how do you handle errors in the “else” block? You guessed that right—you can use another “try-except-else” block within the outer “else” block:
Enter 1st number:1
Enter 2nd number:0
division by zero
 
Catching Python Exceptions with Try-Except-Else-Finally
Finally, we have the last optional block in Python error handling. And it’s literally called “finally”:
Python Try Finally Example
...
try:
<--program code-->
except <--Exception Type 1-->:
<--exception handling code-->
except <--Exception Type 2-->:
<--exception handling code-->
except <-->Exception Type 3-->>:
<--exception handling code-->
...
else:
<--program code to run if "try" block doesn't encounter any error-->
finally:
<--program code that runs regardless of errors in the "try" or "else" block-->
The “finally” block runs whether or not the “try” block’s code raises an exception. If there’s an exception, the code in the corresponding “except” block will run, and then the code in the “finally” block will run. If there are no exceptions, the code in the “else” block will run (if there’s an “else” block), and then the code in the “finally” block will run.
Since the code in the “finally” block always runs, you want to keep your “clean up” codes here, such as:
- Writing status messages to log files
- Resetting counters, lists, arrays
- Closing open files
- Closing database connections
- Resetting object variables
- Disconnecting from network resources
- ... And so on
Here’s an example of using “finally”:
try:
f = open("testfile.txt", 'r')
except FileNotFoundError as fne:
rollbar.report_exc_info()
print(fne)
print('Creating file...')
f = open("testfile.txt", 'w')
f.write('2')
else:
data=f.readline(1)
print(data)
finally:
print('Closing file')
f.close()
Here, the “try” block tries to open a file for reading. If the file doesn’t exist, the exception block shows a warning message, creates the file, and adds a static value of “2” to it. If the file exists, the “else” block reads the first line of its content and prints that out. Finally, the “finally” block closes the file. This happens whether or not the file initially existed.
What we have discussed so far can be summarized in the flowchart below:
 
How Rollbar Can Help Log and Track Python Errors
Rollbar is a continuous code improvement platform for software development teams. It offers an automated way to capture Python errors and failed tests in real time from all application stack layers and across all environments. This allows creating a proactive and automated response to application errors. The diagram below shows how Rollbar works:
Rollbar natively works with a wide array of programming languages and frameworks, including Python. Python developers can install pyrollbar, Rollbar’s SDK for Python, using a tool like pip, importing the Rollbar class in the code, and sending Python exceptions to Rollbar. The code snippet below shows how easy it is:
import rollbar
rollbar.init('POST_SERVER_ITEM_ACCESS_TOKEN', 'dev')
try:
# some code
except TypeError:
rollbar.report_message('There is a data type mismatch', 'fatal')
except:
rollbar.report_exc_info()
Track, Analyze and Manage Errors With Rollbar
Managing errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you to proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing errors easier than ever. Try it today.