Back to articles list Articles
9 minutes read

A Brief Guide to Python Exceptions

Confused about programming errors? In this article, you’ll learn what Python exceptions are, how to deal with them, and how to leverage them when writing and organizing your code.

Errors are very common in programming. In Python, we use the term “exception” for errors that happen during code execution. Be it a typo, a coding distraction, or because the code reached an invalid state, it is inevitable: the more you write Python programs, the more you will have to face and handle Python exceptions.

Python exception handling may sound daunting or esoteric at first, but it is fundamental to your Python learning curve. By properly anticipating potential problems and handling exceptions, we can circumvent the issue and prevent the code from crashing – while keeping users happy and informed. Being able to properly handle Python exceptions will make your code more reliable and comprehensible in the long run.

In this article, we will explore what Python exceptions are, how we can deal with them, and how to leverage them by creating our own exceptions and using them to keep our code organized. We assume you already have a basic understanding of Python – if that’s not the case, we recommend checking out our Learn Programming with Python track as well as this article about Python terms for beginners. Otherwise, let’s get right into it!

What’s in a Python Exception?

In Python, we say that exceptions are raised when an invalid operation is performed, such as trying to add text and numbers together, or dividing a number by zero. Whenever this happens, Python will display the exception message and the code will stop executing at that point.

As an example, consider the script code.py, which contains the single line below:

int('a')

We already expect this code to raise a Python exception – after all, we can’t convert the string 'a' into an integer. If you try executing the script, you’ll see an exception message similar to this:

Traceback (most recent call last):
  File "code.py", line 1, in 
	int('a')
ValueError: invalid literal for int() with base 10: 'a'

The exception message might look scary, but it’s actually very informative. It tells us:

  • What type of Python exception was raised (in this case, a ValueError).
  • Which line triggered the exception (line 1 of the script code.py).
  • Why the exception was raised. This information is conveyed through the Python exception message invalid literal for int() with base 10: 'a', meaning that the string 'a' cannot be converted to an integer.

Common Types of Exceptions in Python

As you may expect, there are many types of Python exceptions out there. Let’s look at some Python exceptions that you will run into most often, along with code samples that will raise them.

SyntaxError

This exception appears when you write Python code with the wrong syntax. Common causes for this are forgetting to use colons on if ... : statements or leaving unmatched braces/parentheses in the code. Look at these examples:

# Missing a colon at the end of the if statement
value = 10
if value < 5  	# SyntaxError: invalid syntax

# Different types of braces/parentheses on each side
values = [2, 5, 4, 9, 10)  	# SyntaxError: closing parenthesis ')' does not match opening parenthesis '['

If you run into this Python exception, the solution is to search for syntax errors in your code and fix them.

TypeError

This exception indicates that you tried to perform an operation using the wrong data type. As the following example demonstrates, you cannot “add” a number and a string:

value = 5
name = 'John'

print(value + name)  	# TypeError: unsupported operand type(s) for +: 'int' and 'str'

This exception often appears when you try to perform an operation with a None value – which is a type that accepts no operations. Read this article to find out more about the Python None value.

ValueError

This exception appears when you provide the correct data type for an operation but with an invalid value. For example, we cannot calculate for the logarithm of a negative number:

import math

math.log(-1)  	# ValueError: math domain error

ZeroDivisionError

As the name suggests, this exception happens when you try to divide a number by zero:

2 / 0  		# ZeroDivisionError: division by zero

You can think of it as a “specialized” version of a ValueError.

IndexError

This exception happens when you provide an invalid index for a sequence – for example, if a list has only 5 elements but you try to get the element at index 100:

values = [1, 5, 11, 17, 22]

print(values[0])
print(values[2])
print(values[100])  	# IndexError: list index out of range

KeyError

This is the dictionary counterpart of the IndexError. The exception is raised when you provide a nonexistent key to a Python dictionary:

grades = {'John': 10, 'Mary': 8, 'Steve': 9}

print(grades['John'])
print(grades['Steve'])
print(grades['Mark'])  		# KeyError: 'Mark'

How to Handle Exceptions in Python

By now, you probably have got a good idea on how Python exceptions work and what they mean. But how do we deal with them?

Sure, some exceptions are straightforward: If I run into a SyntaxError, the solution is to simply fix the syntax in my own code. But what if I don’t know what keys are in a dictionary? What if I need to calculate the logarithm of a number received from user input, which may or may not be negative?

Enter the try/except block. This is a special code block that tries to run a line (or some lines) of code that we know may raise a Python exception. If the exception is indeed raised, it enters the except block, which deals with the situation and keeps the code from crashing.

Here’s an example of how this looks:  

import math

number = 5

print('Calculating the logarithm of', number)
try:
    print(math.log(number))
except ValueError:
    print('Unable to calculate logarithm of', number, 'because it is a negative number. Please provide another number.')

Run the code above and change the line number = 5 to a negative value. As we’ve seen before, the logarithm of a negative number does not exist, leading to a ValueError in our code. However, by placing the line math.log(number) under the try: block, we can anticipate and handle this issue.

Whenever a ValueError triggers, the code simply skips straight into the except ValueError: block. In that block, we inform the user that they should provide another number for the script. Instead of crashing, our program now handles the situation gracefully and informs the user. Crisis averted!

Handling Python Exceptions Like a Pro

We now have a basic understanding of using try/except blocks for Python exception handling. But these code blocks can be expanded in a couple of ways, which we will explore below.

You can use multiple except blocks to handle different types of exceptions. In the code below, we calculate the logarithm of x divided by y while handling the potential exceptions ZeroDivisionError and ValueError inside their respective except blocks. Try running the code after changing the value of x or y and you will see the corresponding exception being handled:

x = 1
y = -2

try:
	value = x / y
	print(math.log(value))
except ZeroDivisionError:
	print('Cannot divide by zero')
except ValueError:
	print('Cannot calculate log of negative value')

It’s also possible to bundle two or more exceptions into a single except block using parentheses:

x = 1
y = -2

try:
	value = x / y
	print(math.log(value))
except (ZeroDivisionError, ValueError) as e:
	print('Got an error:', e)

Since we don’t know beforehand whether the code will trigger a ZeroDivisionError or a ValueError, we use the except ... as e construct to print the message contained in the exception being handled. For example, if the code triggers a ZeroDivisionError, the printed message becomes "Got an error: division by zero".

Another option is to use except Exception in order to handle (virtually) any Python exception in a single block:

x = 1
y = -2

try:
	value = x / y
	print(math.log(value))
except Exception as e:
	print('Got an error:', e)

But beware: while this “catch all” exception handling can be useful in certain situations, it may actually make your code harder to understand and debug. An except Exception block may end up hiding exceptions that you didn’t even consider beforehand!

Finally, there are two optional blocks to a try/except block:

  • The else block, which only executes if no exceptions were raised in the previous try block.
  • The finally block, which always executes – no matter if a Python exception was raised or not.

Here’s how our example looks with the blocks above added to it. (Once again, change the values of x and y to see different messages being printed.)

x = 1
y = -2

try:
	value = x / y
	log_value = math.log(value)
except ZeroDivisionError:
	print('Cannot divide by zero')
except ValueError:
	print('Cannot calculate log of negative value')
else:
	print('Log value is', log_value)
finally:
	print('Code has finished')

As you can see, log_value is calculated inside the try block. During this process, any exceptions that we anticipated will be caught by the corresponding except block. If no exceptions are raised, the code enters the else block and displays the log_value, which was successfully calculated. Afterwards, the finally block triggers and displays the ending message – irrespective of whether the previous code raised an exception.

Create and Raise Your Own Exceptions in Python

If you need more flexibility in your error handling, Python allows you to define custom exception types. You just need to create a class with the name of your custom exception:

class NumberTooLargeError(Exception):
    pass

Don’t worry if you don’t know what classes are yet – you won’t really need to know about them in order to create and use custom exceptions.

Afterwards, you can raise them by using the raise keyword and they will behave just like any other Python exception. For example, the code below raises an exception displaying information whenever the value of x is above a certain limit:

x = 10
limit = 100

if x > limit:
    message = f"Value {x} is above the limit {limit}"
    raise NumberTooLargeError(message)

If we change the value x to 200, we get this error message:

NumberTooLargeError: Value 1000 is above the limit 200

Now, you might be wondering Why would I want to make my own program crash?. The answer really depends on what you are trying to achieve with your code.

If you’re writing code that other people may use, it is useful to raise exceptions that clearly indicate why a given operation is invalid. Whoever is using that code can then decide whether they need to change something in their own code or if they want to use a try/except block to circumvent your custom exceptions. In any case, it always pays off to keep your code clear and organized!

Mastering Exceptions in Python

We just went over many aspects of exception handling in Python. But don’t let your Python learning journey stop here! If you’re trying to learn further, why not challenge yourself with our Python Basics Practice course? You can also read this article for ideas on how to practice your Python skills before a job interview.