Exception Handling in Python: From Basic to Advanced, Then Tricks

One important aspect of Python Programming is exception handling, which refers to the way that errors and unexpected events are handled during the execution of a program. Exception handling is essential for writing robust and reliable code, as it enables programmers to handle errors and exceptions in a structured and controlled manner.
In this article, I will provide a comprehensive guide to Python exception handling, covering everything from basic try-except blocks to more advanced techniques. Whether you are new to Python programming or an experienced developer (You can start from section 3), this article will provide you with a complete overview of exception handling in Python, along with some useful tricks and tips that you may not have encountered before. So, whether you are just starting out with Python or looking to improve your exception handling skills, this article is the perfect resource to help you get started.
1. The Basics

1.1 The Simplest Exception Handling
Let's start with the simplest exception handling in Python. Basically, we have a piece of code that may have any exceptions during the run time, we can put them in the "try" block. Then, in the "except" block, we can do something with it, such as displaying some message to indicate that there was an error.
try:
# Code that may raise an exception
x = 5 / 0
except:
# Code to handle the exception
print("An error occurred")

Also, please be noticed that the program did run successfully. Although there is an error, but we "caught" the error so that it is not considered "crush".
1.2 Catch Specific Types of Exceptions
Sometimes, the piece of code may potentially result in different types of exceptions. Also, we may want to handle different types of exceptions in different ways. In this case, we can specify the type of error after the except
keyword. Also, we can chain multiple except
blocks to handle more than one type of error.
try:
x = 5 / 0
except ZeroDivisionError:
print("You can't divide a number by zero!")
except:
print("Unknown error occurred")

It is common to have the last except block without an explicit error type. So, if there is no except-block above caught the exception, it will fall in the last one.
try:
x = int("foo")
except ZeroDivisionError:
print("You can't divide a number by zero!")
except:
print("Unknown error occurred")

In this example, the error should actually be a TypeError
because the string "foo"
cannot be converted into a number. Therefore, the ZeroDivisionError
did not catch the exception. So, it falls in the default except
block eventually.
1.3 Access Details of the Exception
Regarding the above "Unknown" error, is there any way to obtain more information from the exception? In other words, although something unexpected happened, do we have such a method to have some clue what is the exception about?
The answer is yes. We can put an argument after the except
keyword and access the details of the exception from this argument variable.
try:
x = int("foo")
except ZeroDivisionError:
print("You can't divide a number by zero!")
except Exception as e:
print("An Error occurred:", e)

In this example, we use Exception
which is the parent class of all types of exceptions after the except
keyword, and catch this exception as the variable e
.
That is equivalent to saying, "please catch any type of exception in the variable e
". Then, we can print the variable to get the message. So, we know that the exception is actually we are trying to convert a literal string to an integer.
2. Still the Basics, but Practical Usage

Now, let's have a look at some practical usage patterns of exception handling in Python. In this section, the demo will be conducted in Python functions.
2.1 Without Exception Handling
What if we don't handle the exception? Of course, the program will crash. Let's have a look at how the "Traceback" tell us about the error.
def divide(x, y):
return x / y
def calculate(a, b):
result = divide(a, b)
print("Result:", result)
calculate(10, 0)

The program starts at the calculate(0, 0)
, then the calculate()
function calls the divide()
function. In the above example, the x / y
in the divide()
caused the ZeroDivisionError
.
Looking at the traceback, the last block tells us where the exception comes from. Since there is no exception handling, it throws the exception back to its parent function calculate()
body. Inside this function, the exception still has not been handled. Therefore, it throws again to its parent, where we called this calculate()
function. The program crashed because the exception is not handled and reached the root level.
2.2 A Program with Exception Handling
Hold on, that means we don't have to handle exceptions whenever it may occur. Instead, we can handle them at a certain and appropriate level.
For example, there is one line of code called one function, inside this function it calls many other functions that may cause different types of exceptions. In this case, we may only need to put this one line of code in the try-except block so that it will be handled.
def divide(x, y):
return x / y
def calculate(a, b):
try:
result = divide(a, b)
print("Result:", result)
except ZeroDivisionError:
print("You can't divide a number by zero!")
calculate(10, 0)

In the above example, we have an exception handling in the calculate()
function. Although the exception happened in the divide()
function, it will throw it to the parent level and be caught in the calculate()
function.
2.3 The Finally Block
Well, I want to make this article a completed guide to Python Exception Handling. So, I guess let's simply explore the finally
block.
Long story short, the code in the finally
block will be executed regardless if there are exceptions.
try:
x = 5 / 1
except ZeroDivisionError:
print("You can't divide a number by zero!")
finally:
print("Calculation finished")

One of the most common use cases of the finally
block is to close resources such as database connections and opened files. This is a very good manner to avoid unexcepted behaviours or memory leaks.
2.4 Raise an Exception Deliberatively
Rather than catching an exception, we can also deliberately raise an exception. This could be very useful for debugging and control flow purposes that allow us to jump to a different part of the code or exit the program.
def calculate(a, b):
try:
raise Exception("I just want to raise an exception!")
except Exception as e:
print(e)
calculate(10, 0)

3. Extra Tricks That You May Not Know

If you're not new to Python, you may have skipped ahead to this section looking for some advanced techniques or knowledge gaps to fill. I hope this section can provide you with new insights and help you further refine your understanding of exception handling in Python.
3.1 Else Block
Do you know that the try ... except ... finally
are not everything in Python exception handling? I guess you may not know that we can also use else
in exception handling. The else
block will only be executed if there is no exception.
try:
x = 5 / 1
except ZeroDivisionError:
print("You can't divide a number by zero!")
else:
print("The result is: ", x)
finally:
print("Calculation finished")

In fact, the else
block is not a must-known thing. Theoretically, we can put the code after the line that may cause the exception. It will run anyway if there is no exception.
However, there are several weak reasons that we may want to use the else
block. Firstly, it may improve readability because it is pretty natural to understand it as such: "if there is an exception, handle it like this, else please execute this code". Secondly, the else
block physically separated the code which may cause exceptions and the code won't.
3.2 Warning Module
This might not be closely related to exception handling. However, some readers may be interested in this. If you have ever used Pandas library, sometimes it will give us warnings if we used some deprecated API or doing things with risks.
How this is done? The answer is to use the warning
module.
import warnings
def calculate(x, y):
try:
result = x / y
except ZeroDivisionError:
print("You can't divide a number by zero!")
else:
if x == result:
warnings.warn("All numbers divide by 1 will remain the same.")
print("Result: ", result)
calculate(10, 1)

3.3 Assertion – Another Way of Raising an Exception
Another related technique in Python is the assertions. It is used to check whether a certain condition is true or false during the execution of a program. If the condition is true, the program continues executing normally. If the condition is false, an AssertionError
is raised, interrupting the normal flow of the program.
def calculate(x, y):
assert y != 0, "You can't divide a number by zero!"
result = x / y
print("Result: ", result)
calculate(10, 0)

The assertion is commonly used for debugging and unit testing in Python. If the condition is satisfied, nothing will happen.
def calculate(x, y):
assert y != 0, "You can't divide a number by zero!"
result = x / y
print("Result: ", result)
calculate(10, 1)

3.4 Custom Exception Type
Sometimes, we may want to define and use custom exception types when we want to provide more specific and informative error messages to the user, or when we want to differentiate between different types of errors that can occur in our code.
We can define a custom exception as simply as follows.
class CustomException(Exception):
def __init__(self, message):
super().__init__(message)
raise CustomException("This is a custom exception")

Of course, we can do whatever we like because this is a customised class.
class CustomException(Exception):
def __init__(self, message):
super().__init__(message)
def __str__(self):
return f"A customised exception has occured: {self.args[0]}"
raise CustomException("This is a custom exception")

Using custom exceptions in Python allows us to exercise our imagination and provides the maximum level of flexibility in handling errors and exceptions in our code.
4. The Suppress Module

In the last section, I want to introduce the suppress
module in the contextlib
. It is built-in into Python, but fewer people know about it and it is rarely used. However, it can be very useful in some cases.
Suppose we have some lines of code that may cause exceptions. However, we may don't care about these exceptions. Therefore, rather than raising these exceptions and handling them, the easiest way is to ignore them, or "suppress" them.
For example, the code below will output nothing.
from contextlib import suppress
with suppress(ZeroDivisionError):
x = 5 / 0
print(x)
The above code uses a with-statement together with the suppress
function. It will ignore all the ZeroDivisionError
happens in the code inside.
Why it is useful? Think about we have a series of user inputs. Some of the input values may not be valid. Suppose we don't care about the invalid inputs at all. Instead, we just want to ignore them and process those that are valid.
Let's simulate the scenario by having a list of items.
nums = [3, -1, -2, 1, 1, 0, 3, 1, -2, 1, 0, -1, -1, -1, 3, -2, -1, 3, '3', -1]
result = 0
for num in nums:
with suppress(ZeroDivisionError, TypeError):
result += 1/num

As shown above, those zeros and strings are simply ignored. The code looks pretty neat and clean.
If you want to explore more for the suppress
module more, I have a particular article that will do a deep dive.
Quick Python Tip: Suppress Known Exception Without Try Except
Summary

In this article, we have explored the different aspects of Python exception handling. There were some useful tricks and tips for handling exceptions, such as using the warning
module and suppressing specific exceptions with the suppress
module.
By mastering exception handling in Python, you can write more robust and reliable code that can handle unexpected events and errors in a structured and controlled way. Whether you are a beginner or an experienced Python developer, understanding exception handling is essential for writing effective and efficient code. I hope that this article has provided you with a comprehensive guide to Python exception handling and some useful tips and tricks to help you improve your exception handling skills.
If you feel my articles are helpful, please consider joining Medium Membership to support me and thousands of other writers! (Click the link above)
Unless otherwise noted all images are by the author