Mastering Python Error Handling: A Comprehensive Guide
With examples from Simple to Advanced
A Note for the eager-minds reading this:
Before diving in, it's important to note that this guide assumes you have:
A fundamental understanding of programming concepts, including the declaration and assignment of variables.
Prior experience with an IDE(Integrated Development Environment) for Python with the ability to add and modify code blocks.
Proficiency in using Git for version control(to save different versions of your code).
Agenda
Understanding Python Exceptions
Introduction to exceptions
Common types of exceptions
The Try-Except Block
Syntax and structure
Handling specific exceptions
Multiple except blocks
Using the else clause
Raising Exceptions
The 'raise' statement
Creating custom exceptions
Best practices for raising exceptions
Exception Handling in Real-World Scenarios
File I/O and exception handling
Network operations and error management
Database interactions and transactions
Cleaning Up with Finally
The role of the 'finally' block
Use cases for 'finally'
Combining 'try', 'except', and 'finally'
Advanced Error Handling Techniques
Handling exceptions in loops
Context managers and the 'with' statement
Error handling in asynchronous code
Understanding Python Exceptions
Introduction to Exceptions
In Python, Exception is a fundamental aspect of the language. They are events that occur during the execution of a program that disrupts the normal flow of the program's instructions. When a Python script encounters a situation that it cannot cope with, it raises an exception. An exception in a more technical way is a Python object that represents an error.
While it might seem counterintuitive to consider error-producing mechanisms as a feature, exceptions play a crucial role in robust software tools. They don't cause a crash with a traceback (a sequence of text indicating the error's origin and endpoint). Instead, exceptions aid in decision-making by generating descriptive error messages, enabling you to handle both anticipated and unforeseen issues effectively.
For example, you want to get the division of two numbers. The algorithm of the code will go like this:
# Code that may raise an exception
x = 1
y = 0
z = x / y
print("The operation x/y equals:", z)
Output with a traceback:
Traceback (most recent call last):
File "c:\Users\sample.py", line 4, in <module>
z = x / y
~~^~~
ZeroDivisionError: division by zero
That output is a Python traceback, which is a report containing the function calls made in your code at a specific point. Here are the key parts:
Traceback (most recent call last):
This line signifies the start of the traceback.File "c:\Users\sample.py", line 4, in <module>
: This line tells you the file in which the error occurred (sample.py
), the line number where the error occurred (line 4
), and the scope where the error occurred (<module>
) - more like say a file that contains functions and variables.z = x / y
: This is the line of code that caused the error.ZeroDivisionError: division by zero
: This is the type of error that occurred (ZeroDivisionError
), along with a message that gives more information about the error (division by zero
).
Putting all these key parts together, the traceback is telling you that a ZeroDivisionError
occurred on line 4 of the file sample.py
, on the code z = x / y
, because you tried to divide a number by zero, which is mathematically undefined. This information can be used to debug and fix the error.
Now that's a lot of info to take in
We have two classes of Exceptions in Python:
Built-in Exceptions and
user-defined Exceptions
In this guide, we'll focus more on built-in exceptions, and there will be an entirely different blog post on User-defined exceptions so we can have a clear margin there.
Common Types of Built-in Exceptions
Python has numerous built-in exceptions that force your program to output an error when something in the program goes wrong. Some common types of exceptions include: ImportError
(when an import statement fails) IndexError
(when a sequence subscript is out of range) TypeError
(when an operation or function is applied to an object of inappropriate type) ValueError
(when a built-in operation or function receives an argument that has the right type but an inappropriate value).
To see a list of other exceptions in python, follow this link to the Python documentation website for an extensive read of Python Built-in Exceptions
The Try-Except Block
Now, we know what an exception is in Python and how to break down the info to a more comprehensive level. But, to think about it, it is a very tedious process to go through each line to detect what the problem is. This is why Python provides a rich feature to help write codes better by catching errors in time.
The try
and except
statements in Python are used to catch and handle exceptions(which we know in our layman's term is an error). Python executes code following the try
statement as a "normal" part of the program. The code that follows the except
statement is the program's response to any exceptions in the preceding try
clause.
Here's a simple implementation from the previous example when attempting to divide by zero:
try:
# Code that may raise an exception
x = 1
y = 0
z = x / y
except ZeroDivisionError:
# What to do when the exception is raised
print("You can't divide by zero!")
Output:
You can't divide by zero!
In this example, the code within the try
block is executed line by line. If at any point an exception is raised, the execution stops and the code within the except
block is run. If no exception is raised, the except
block is skipped. And since an exception is raised, it runs the code in the except block.
Now, that's a big chess move
Syntax and Structure
Python's exception-handling mechanism is built around the try/except
block. The syntax is as follows:
try:
# Code that may raise an exception
except ExceptionType:
# Code to execute if an exception of type ExceptionType is raised
Here, ExceptionType
is the type of exception that you want to catch. If an exception of this type (or of a derived type) is raised inside the try
block, the code inside the except
block will be executed.
Handling Specific Exceptions
You can catch specific exceptions by specifying their type in the except
clause. For example, to catch a ZeroDivisionError
, you can do:
try:
x = 1
y = 0
z = x / y
except ZeroDivisionError:
print("You can't divide by zero!")
Output:
You can't divide by zero!
Multiple Except Blocks
You can have multiple except
blocks to handle different types of exceptions separately. For example:
try:
# Some code...
except ZeroDivisionError:
print("You can't divide by zero!")
except TypeError:
print("Wrong type!")
In this case, if a ZeroDivisionError
is raised, the first except
block will be executed. If a TypeError
is raised, and the second except
block will be executed.
Implementation:
try:
x = 1
y = "2"
z = x / y
except ZeroDivisionError:
print("You can't divide by zero!")
except TypeError:
print("Wrong type!")
Output:
Wrong type!
In this example, we're trying to divide an integer (x
) by a string (y
), which is not allowed in Python and raises a TypeError
. The except
block for TypeError
catches this exception and prints "Wrong type!". If y
was 0, it would raise a ZeroDivisionError
and the output would be "You can't divide by zero!".
Using the Else Clause
The else
clause in a try/except
block is used to specify a block of code to be executed if no exceptions are raised in the try
block. For example:
try:
x = 1
y = 2
z = x / y
except ZeroDivisionError:
print("You can't divide by zero!")
else:
print("The division was successful!")
Output:
The division was successful!
Raising Exceptions
You can manually raise exceptions using the raise
statement. This is useful when you want to trigger an exception if a certain condition is met. For example:
x = 10
if x > 5:
raise Exception('x should not exceed 5.')
Output:
Exception: x should not exceed 5.
The 'raise' statement
In Python, you can manually trigger exceptions using the raise
statement. This is useful when you want to indicate that an error has occurred, just like in the raise implementation from the last section:
x = 10
if x > 5:
raise Exception('x should not exceed 5.')
Output:
Exception: x should not exceed 5.
Creating custom exceptions
You can create your own exceptions in Python by creating a new exception class. This class should derive from the built-in Exception
class. Here's an example:
class CustomError(Exception):
pass
try:
raise CustomError("This is a custom exception")
except CustomError as e:
print(e)
Output:
Exception: This is a custom exception.
Best practices for raising exceptions
When raising exceptions, it's important to provide meaningful error messages so that the person reading the traceback can understand what went wrong. Also, it's a good practice to create and use custom exceptions when you need to raise an exception that doesn't fit into any of the built-in exception categories.
Exception Handling in Real-World Scenarios
In real-world scenarios, exception handling is crucial for building robust programs. Unhandled exceptions can cause your program to crash. By handling exceptions, you can ensure that your program can recover from errors and continue running. Here's an example of how you might handle exceptions in a real-world scenario:
try:
# some code that might raise an exception
file = open('non_existent_file.txt', 'r')
except FileNotFoundError:
print("The file does not exist.")
Output:
Exception: The file does not exist
When you run this code, it will output: "The file does not exist." because the file 'non_existent_file.txt' does not exist. This demonstrates how you can handle exceptions to prevent your program from crashing when an error occurs.
File I/O and Exception Handling
File input/output (I/O) operations are a common source of errors in programming. Python provides built-in functions for file I/O, such as open()
, read()
, and write()
. These functions can raise exceptions, such as FileNotFoundError
(in the case where the file is not created yet and you want to read form it) or PermissionError
(when the file is set to only be reable or writable or executable only), if something goes wrong.
-rw-r--r-- 1 ahmaddev 197121 35 Aug 30 07:54 online.txt
-rw-r--r-- 1 ahmaddev 197121 98 Nov 7 16:09 sample.py
-rw-r--r-- 1 ahmaddev 197121 118 Aug 30 07:28 sampletext.txt
-rw-r--r-- 1 ahmaddev 197121 475 Oct 8 08:19 test.py
In the file directory above:
-rw-r--r-- 1 Test 197121 475 Oct 8 08:19 test.py
The file permission is divided into three parts in the format "-rw-r--r--" which stands for:
owner (-rw-): This means that the owner, who in this case is
Test
has a read-and-write access (can view and change the content of the file - copy from and to)group (r--): any group who has access to the directory will only have write only access (which means they can't change anything in the file, but can view the content of the file and copy from it)
others (r--): Other people who has access to the directory apart from the owner and group has only a read only access, just like in the second bullet point
'r' represents read access, 'w' represents write access and 'x' represents an executable access. The topic of file manipulation and permission is a broader scope in DevOps itself. So we'll reduce ourselves to the scope of this guide.
To handle these exceptions, you can use a try/except
block. For example:
try:
file = open('non_existent_file.txt', 'r')
except FileNotFoundError:
print("The file does not exist.")
Network Operations and Error Management
Network operations, such as sending a request to a server or receiving data from a server, can also raise exceptions. For example, the requests
library in Python raises a requests.exceptions.RequestException
if a network request fails. You can catch and handle this exception like this:
import requests
try:
response = requests.get('https://non_existent_website.com')
except requests.exceptions.RequestException:
print("The request failed.")
Database Interactions and Transactions
When interacting with a database, you might encounter exceptions related to the database connection, the SQL queries, or the data itself. Most database libraries in Python provide their own set of exceptions that you can catch and handle. For example, the sqlite3
library raises a sqlite3.Error
if a database operation fails:
import sqlite3
try:
conn = sqlite3.connect('non_existent_database.db')
except sqlite3.Error:
print("The database operation failed.")
Cleaning Up with Finally
The finally
clause in a try/except
block is used to specify a block of code that will be executed no matter whether an exception is raised or not. This is often used for cleanup actions that must always be completed, such as closing a file or a database connection:
try:
file = open('file.txt', 'r')
# Some code...
finally:
file.close()
In this example, the file.close()
statement will be executed even if an exception is raised inside the try
block. This ensures that the file is properly closed even if an error occurs.
The Role of the 'Finally' Block
The finally
block in Python is part of the try/except
statement, which is used for exception handling. The finally
block contains code that is always executed, whether an exception is raised or not. This is often used for cleanup actions, such as closing a file or a database connection.
Here's an example:
try:
file = open('file.txt', 'r')
# Some code...
finally:
file.close()
When you run this code, it will always close the file, even if an error occurs in the try
block.
Use Cases for 'Finally'
The finally
block is useful in scenarios where certain code must be executed regardless of whether an error occurred or not. This is often the case for cleanup actions, such as:
Closing files or network connections.
Releasing resources, such as memory or hardware.
Restoring the state of the program or the system.
Combining 'Try', 'Except', and 'Finally'
You can combine try
, except
, and finally
in a single statement to handle exceptions, execute certain code regardless of whether an exception occurred, and specify different actions for different types of exceptions. Here's an example:
try:
x = 1
y = 0
z = x / y
except ZeroDivisionError:
print("You can't divide by zero!")
finally:
print("This is always executed.")
When you run this code, it will output:
You can't divide by zero!
This is always executed.
Advanced Error Handling Techniques
Python provides several advanced techniques for error handling, such as:
Chaining exceptions: You can raise a new exception while preserving the original traceback.
Creating custom exceptions: You can create your own exception types to represent specific errors in your program.
Using the
else
clause: You can use theelse
clause in atry/except
statement to specify a block of code to be executed if no exceptions are raised.
Handling Exceptions in Loops
When an exception is raised inside a loop, it interrupts the loop. However, you can catch the exception and continue with the next iteration of the loop. Here's an example:
for i in range(5):
try:
if i == 2:
raise Exception("An error occurred!")
else:
print(i)
except Exception:
continue
When you run this code, it will output:
0
1
3
4
Context Managers and the 'With' Statement
A context manager is an object that defines methods to be used in conjunction with the with
statement, including methods to handle setup and teardown actions. When used with file operations, it can help ensure that files are properly closed after use, even if errors occur. Here's an example:
try:
with open('file.txt', 'r') as file:
# Some code...
except FileNotFoundError:
print("The file does not exist.")
In this example, the with
statement automatically closes the file after the nested block of code is executed, even if an exception is raised inside the block.
Error Handling in Asynchronous Code
Asynchronous code can also raise exceptions, and these exceptions can be caught and handled just like in synchronous code. However, because asynchronous code can have multiple execution paths running concurrently, exceptions may need to be handled in each execution path separately. This can be done using try/except
blocks inside asynchronous functions or coroutines. But we won't go into the implementation details since it goes beyond the scope of this topic.
You've come to the end of this Guide!!!
If you enjoyed this guide, do well to stay updated on our latest Technical content and installation guides. You can follow the Technical writer on Twitter, LinkedIn, Dev.to, Medium