Throwing Exceptions in Python

April 21st, 2021 • By Rollbar Editorial Team

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.

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 <module>
    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 do You 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 <module>
    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:

Use AssertionError to raise and throw an exception in Python

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 <module>
    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

<program code>
...
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 <module>
    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)

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)
    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)
    exit()
except ZeroDivisionError as zde:
    print(zde)
    exit()
except TypeError as te:
    print(te)
    exit()
except:
    print('Unexpected Error!')
    exit()

print (result)

We can test this program with different values:

nter 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:

...
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)
    exit()
except ZeroDivisionError as zde:
    print(zde)
    exit()
except TypeError as te:
    print(te)
    exit()
except:
    print('Unexpected Error!')
    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 <module>
    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:

num0 = 10

try:
    num1 = int(input("Enter 1st number:"))
    num2 = int(input("Enter 2nd number:"))
except ValueError as ve:
    print(ve)
    exit()
except ZeroDivisionError as zde:
    print(zde)
    exit()
except TypeError as te:
    print(te)
    exit()
except:
    print('Unexpected Error!')
    exit()
else:
    try:
        result = (num1 * num2)/(num0 * num2)
    except Exception as e:
        print(e)
        exit()
    else:
        print (result)

Now this handles a division by zero error gracefully:

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:
    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:

Python exception handling logical flow

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 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 automated testing model

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()

Get the latest updates delivered to your inbox.