Python exception handling is the process of identifying and responding to errors in a program. In other words, it is a way to deal with errors that might occur in your program. In this article, you will learn how to handle errors in Python by using the Python try
and except
keywords. It will also teach you how to create custom exceptions, which can be used to define your own specific error messages.
Table of Contents
What is an exception?
An exception is a condition that arises during a program’s execution. It is a signal that something unexpected happened. Python represents exceptions by an object of a specific type.
In Python, all built-in, non-system-exiting exceptions are derived from the Exception
class. Exceptions have their own descriptive names. For example, if you try to divide a number by zero, you will get a ZeroDivisionError
exception, which is also a subclass of the Exception
class.
For a complete hierarchy of all exceptions, you can view the Python manual if you’re interested. Here’s a small excerpt from this hierarchy to illustrate:
BaseException +-- SystemExit +-- KeyboardInterrupt +-- Exception +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError .....
If your knowledge about objects, classes, and inheritance is a bit rusty, you may want to read my article on objects and classes and my article on inheritance first.
Python try except
When something unexpected occurs, we can raise an exception at the point of the error. When an exception is raised, Python stops the current flow of execution and starts looking for an exception handler that can handle it. So what is an exception handler? Here’s where the try and except statements come into play.
As the illustration demonstrates, we can create a code block by starting with a try statement. This means: try to run this code, but an exception might occur.
After our try block, one or more except blocks must follow. This is where the magic happens. These except blocks can catch an exception, as we usually call this. In fact, many other programming languages use a statement called catch
instead of except
. Each except
block can handle a specific type of exception.
Remember: classes are hierarchical. For that reason, exceptions are hierarchical as well. Hence, except blocks must go from the most specific, like a ZeroDivisionError
, to less specific, like an ArithmeticError
.
To demonstrate this, imagine what happens when we start with an except block that catches Exception. This first block would catch everything because most exceptions inherit from this one, rendering the other except blocks useless.
Now let’s first go back to raising an exception. When an exception is raised, the exception handler that’s able to handle the exception can be nearby, but it can also be in a different module. What’s important to realize is that Python won’t just scan your code randomly for an exception handler. Instead, the handler should be somewhere in the call stack.
Please forget about else
and finally
for now. I’ll explain them in detail further down in this article. We first need to discuss call stacks to understand how an exception finds its way to an exception handler.
Call stack
A call stack is an ordered list of functions that are currently being executed. For example, you might call function A, which calls function B, which calls function C. We now have a call stack consisting of A, B, and C. When C raises an exception, Python will look for an exception handler in this call stack, going backward from end to start. It can be in function C (closest to the exception), in function B (somewhat farther), in function A, or even at the top level of the program where we called function A.
If Python finds a suitable except block, it executes the code in that block. If it doesn’t find one, Python handles the exception by itself. This means it will print the exception and exit the program since it has no clue what to do with it.
I hope you’re still with me! If not, no worries. The examples on this page will hopefully clarify everything. You might want to revisit this section after finishing the entire article.
Catching exceptions with try except
Let’s finally write some actual code! To handle an exception, we need to catch it. As we just learned, we can catch an exception by using the try
and except
keywords. When an exception occurs while we are inside the try
block, the code in the except
block is executed.
A simple example
Let’s try a simple example first. As you hopefully know, we can’t divide by the number zero. If we do so anyway, Python will throw and exception called ZeroDivisionError
, which is a subclass of ArithmeticError
:
try: print(2/0) except ZeroDivisionError: print("You can't divide by zero!")
If you call a Python function inside the try block, and an exception occurs in that function, the flow of code execution stops at the point of the exception and the code in the except block is executed. Try doing this again without try and except. You’ll see that Python prints the exception for us. You can do so in the following code crumb:
Also, note that Python prints the error to stderr if you don’t handle the exception yourself. In the crumb above, this is visible because the output appears in an ‘Error’ tab instead of an ‘Output’ tab.
Catching IOError
Let’s try another, more common example. After all, who divides a number by zero, right?
Exceptions are likely to occur when interacting with the outside world, e.g., when working with files or networks. For example, if you try to open a file with Python, but that file doesn’t exist, you will get an IOError
exception. If you don’t have access to a file due to permissions, you will again get an IOError
exception. Let’s see how to handle these exceptions.
Assignment
Please do the following:
- Run the code below, and notice the file name (it doesn’t exist). See what happens.
- Alter the file name to myfile.txt file and rerun the code. What happens now?
Alternatively, here’s the code to copy/paste:
try: # Open file in read-only mode with open("not_here.txt", 'r') as f: f.write("Hello World!") except IOError as e: print("An error occurred:", e)
Answers
The file is not found in the first case. You should get this output:
An error occurred: [Errno 2] No such file or directory: 'not_here.txt'
You’ll still get an error after creating the file in the second case. This time, we’re trying to write to a file that is opened in read-only mode. For more information on these modes, read the article on opening, reading, and writing files with Python. The error should look like this:
An error occurred: not writable
Although this is an error, it’s not written to the stderr output of the operating system. That’s because we handled the exception ourselves. If you removed the try.. except from the code completely and then try to write to the file in read-only mode, Python will catch the error, force the program to terminate, and show this message:
Traceback (most recent call last): File "tryexcept.py", line 3, in <module> f.write("Hello World!") io.UnsupportedOperation: not writable
The finally and else blocks
Remember the other two blocks that I asked you to forget for a while? Let’s look at those now, starting with the finally
block.
The finally block in try-except
The finally
block is executed regardless of whether an exception occurs or not. Finally
blocks are useful, for example, when you want to close a file or a network connection regardless of what happens. After all, you want to clean up resources to prevent memory leaks.
Here’s an example of this at work, in which we open a file without using the with statement
, forcing us to close it ourselves:
try: # Open file in write-mode f = open("myfile.txt", 'w') f.write("Hello World!") except IOError as e: print("An error occurred:", e) finally: print("Closing the file now") f.close()
You can also try this interactive example:
You should see the ‘Closing the file now’ message printed on your screen. Now change the writing mode to ‘r’ instead of ‘w’. You’ll get an error since the file does not exist. Despite this exception, we try to close the file anyway thanks to the finally block. This in turn goes wrong too: a NameError
exception is thrown because the file was never opened, and hence f
does not exist. You can fix this with a nested try.. except NameError
. Try it for yourself.
The else block in try-except
In addition to the except
and finally
blocks, you can add an else block. The else block executes only when no exception occurs. So it differs from the finally block, since finally executes even if an exception occurs.
When should you use the else
block? And why shouldn’t you just add extra code to the try
block? Good questions!
According to the Python manual, using the else clause is better than adding additional code to the try clause. But why? The reasoning is that it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try and except statements in the first place. I admit I don’t use else blocks very often myself. Also, I find them somewhat confusing, especially for people coming from other languages.
Common Python exceptions
Some exceptions are so common that you’ll inevitably encounter them. Here are a few of the most common ones:
Exception name | When you’ll encounter it | When you encounter it |
SyntaxError | Raised when there is an error in Python syntax. If not caught, this exception will cause the Python interpreter to exit. | pritn('test') |
KeyError | Raised when a key is not found in a dictionary. | d = { 'a': 1} |
IndexError | Raised when an index is out of range. | lst = [1, 2, 3] |
KeyboardInterrupt | Raised when the user hits the interrupt key (Ctrl+C) | Pressing control+c |
If you like, you can try to evoke these exceptions intentionally. I promise you that you will encounter these countless times in your Python programming career. Understanding what they mean and when they occur will greatly help you debug your code.
Exception best practices
Now that we know the mechanics of handling exceptions, I’d like to share a couple of best practices with you.
Don’t use blank except blocks
I’ve written about this in the blog post ‘How not to handle exceptions in Python‘. Don’t use a blank block when you want to catch a broad range of exceptions. By this, I mean something like:
try: ... except: print("An error occurred:")
You might encounter this in code samples on the web. If you do, make a habit of improving the exception handling. Why should you, and how can you improve code like the example above?
All exceptions, including system exceptions, inherit from a class called BaseException
. If an except
clause mentions a particular class, that clause also handles any exception classes derived from that class. An empty except
is equivalent to except BaseException
, hence it will catch all possible exceptions.
So, although the syntax is allowed, I don’t recommend it. E.g., you’ll also catch KeyboardInterrupt and SystemExit exceptions, which prevent your program from exiting. Instead, use a try block with a list of explicit exceptions you can handle. Or, if you really need to, catch the Exception
base class to handle almost all the regular exceptions, but not the system ones.
If you’re feeling adventurous, you can try to catch all exceptions and see what happens:
from time import sleep while True: try: print("Try and stop me") sleep(1) except: print("Don't stop me now, I'm having such a good time!")
You’ll probably need to close your terminal to stop this program. Now change the except block to catch Exception
. You will still catch almost all exceptions, but the program will exit on system exceptions like KeyboardInterrupt
and SystemExit
:
from time import sleep while True: try: print("Try and stop me") sleep(1) except Exception: print("Something went wrong")
It’s better to ask for forgiveness
In Python, you’ll often see a pattern where people simply try if something works, and if it doesn’t, catch the exception. In other words, it’s better to ask for forgiveness than permission. This is in contrast to other languages, where you preferably ask for permission. For example, in Java, exceptions can slow down your program, and you “ask for permission” by doing checks on an object instead of simply trying.
To make this more concrete: in Python, we often just try to access the key in a dictionary. If the key doesn’t exist, we’ll get an exception and handle it. Suppose we just converted some externally provided JSON to a dictionary and now start to use it:
import json user_json = '{"name": "John", "age": 39}' user = json.loads(user_json) try: print(user['name']) print(user['age']) print(user['address']) ... except KeyError as e: print("There are missing fields in the user object: ", e) # Properly handle the error ...
This will print the error:
There are missing fields in the user object: 'address'
We could have added three checks (if 'name' in user
, if 'age' in user
, etc.) to make sure that all the fields are there. But this is not a good practice. It potentially introduces a lot of code just to check if keys exist. Instead, we ask for forgiveness in our except block once, which is much cleaner and more readable. And if you worry about performance: exceptions don’t take up that many CPU cycles in Python. Lots of comparisons are in fact slower than catching a single exception (if it occurs at all!).
Create custom exceptions
All built-in, non-system-exiting exceptions are derived from the Exception
class as we learned before. All user-defined exceptions should also be derived from this class. So if we want to create our own exceptions, we need to create a subclass of the Exception
class.
For example, if you want to create an exception that indicates that a user was not found, you can create a UserNotFoundError
exception. This would, in its most basic form, look like this:
class UserNotFoundError(Exception): pass
This inherits all the properties and methods of Exception
, but we give it a new name to distinguish it from the Exception
class. This way, we’ll be able to specifically catch it with an except block.
The name of this exception clearly tells us the type of problem that was encountered, so as an added bonus, it functions as a form of code documentation as well. Just like well-named variables and functions, a well-named exception can be a big difference when reading back your code.
We’ll use this class in the example that follows.
Raising (or throwing) exceptions
We know some built-in exceptions and how to create custom exceptions. We also know how to catch exceptions with try and except. What’s left, is what’s called raising or throwing an exception. You can raise an exception yourself with the raise keyword.
In the example below, we use your previously defined UserNotFoundError
. We call a function, fetch_user
, that fetches some user data from an imaginary database. If the user is not found, this database returns None. We decided that we don’t want to return None, which would force the caller to check for None every time. Instead, we use our custom UserNotFoundError
.
class UserNotFoundError(Exception): pass def fetch_user(user_id): # Here you would fetch from some kind of db, e.g.: # user = db.get_user(user_id) # To make this example runnable, let's set it to None user = None if user == None: raise UserNotFoundError(f'User {user_id} not in database') else: return user users = [123, 456, 789] for user_id in users: try: fetch_user(user_id) except UserNotFoundError as e: print("There was an error: ", e)
Assignment
Here’s a little assignment. We could have used a regular Exception object instead. That way, we don’t need to define a custom one. Why is this a bad idea?
Answer
You can in fact raise a regular exception, e.g. with raise Exception('User not found')
. But if you do, you need to catch all exceptions of type Exception. And as we know, there are a lot of those. Chances are you inadvertently catch some other exception that you’re not able to handle. For example, the database client might throw a DatabaseAuthenticationError
which is also a subclass of Exception
.
How to print a Python exception
You can print exceptions directly as long as you catch them properly. You may have seen examples of this above already. To be clear, here’s an example of how to catch and print an exception:
try: ... except Exception as e: print("There was an error: ", e)
If you’d like to print the call stack, just like Python does when you don’t catch the exception yourself, you can import the traceback module:
import traceback try: ... except Exception: traceback.print_exc()
Keep learning
Here are some more resources to deepen your knowledge:
- My blog article ‘How not to handle exceptions in Python‘
- Introduction to Python functions
- Objects and classes, and Python inheritance
- The official documentation on exceptions.
- The official documentation on errors.