Python — Exception Handling (57/100 Days of Python)
Python is a high-level programming language that allows developers to write code in a clear and concise manner. However, even the best-written code can still result in errors or exceptions that can crash your program. This is where Python’s exception handling comes in. In this tutorial, we will cover what exceptions are, how to handle them in Python, and the best practices to follow.
What are Exceptions in Python?
In Python, exceptions are events that occur during the execution of a program that disrupt the normal flow of instructions. When an exception occurs, Python raises an error message that indicates the type of exception and the line number where the exception occurred. The error message allows you to identify the problem and make corrections to your code:
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print('Division by zero!')
else:
print('Result is', result)
finally:
print('Executing finally clause')
divide(2, 0)
# Division by zero!
# Executing finally clause
divide(2, 2)
# Result is 1.0
# Executing finally clause
In this example, we define a function divide
that takes two arguments, x
and y
, and returns their quotient. However, when we call the function with x=2
and y=0
, a ZeroDivisionError
occurs, and Python raises an error message indicating that division by zero is not allowed.
We will talk about different types of exceptions, what are the else
and finally
keywords, and some specifics of exception handling in Python in a little bit.
How to Handle Exceptions in Python
Python provides a way to handle exceptions through the use of the try
and except
statements. The try
statement encloses a block of code that may raise an exception, while the except
statement catches the exception and allows the program to continue executing:
try:
# code block that may raise an exception
except:
# code block to handle the exception
Our previous code, for instance, handled the ZeroDivisionError
in the except
block.
If we don’t specify any type of error (like ZeroDivisionError
) then the except statement will capture all the errors. So, the block will be executed in case of any error in the try
block. This is called a generic exception handler or a catch-all exception handler.
So, we place the vulnerable code inside a try
block. We place the code that needs to be executed in case an exception occurs in the except
block, and we can specify a type of exception that needs to be captured by the except
block.
Python also provides an else
block that executes if no exceptions were raised in the try
block. Additionally, the finally
block executes regardless of whether an exception occurred:
x, y = 2, 1
try:
x / y
except ZeroDivisionError:
print('Error: Division by zero!')
else:
print('Division successful!')
finally:
print('End of try-except block')
In this example, the else
block executes because no exception was raised. The finally
block always executes, whether an exception occurred or not.
Generic Exception Handling
In Python, we can use the except
keyword without specifying the type of exception to catch any type of exception that may occur in our code. This is called a generic exception handler or a catch-all exception handler.
Using a generic exception handler can be useful when we are not sure about the type of exception that may occur, or when we want to catch all exceptions in one place. However, using a catch-all exception handler can also make it harder to debug code, as we may not know exactly which type of exception occurred and why.
It’s generally recommended to catch specific exceptions whenever possible, as this makes the code easier to read and maintain. Additionally, it’s good practice to log exceptions instead of printing error messages, so we can get more information about the error and track down issues more easily.
Built-in Exceptions in Python
In Python, there are many built-in exceptions that are commonly used to handle errors and exceptions in code. Here are some of the most popular built-in exceptions:
TypeError
: Raised when an operation or function is applied to an object of inappropriate type.ValueError
: Raised when an operation or function is applied to an object with the correct type, but with an inappropriate value.IndexError
: Raised when an index is out of range.KeyError
: Raised when a key is not found in a dictionary.NameError
: Raised when a variable or name is not defined.ZeroDivisionError
: Raised when division by zero occurs.FileNotFoundError
: Raised when a file or directory is not found.AssertionError
: Raised when an assertion fails.OverflowError
: Raised when a mathematical operation overflows.SyntaxError
: Raised when there is a syntax error in the code.AttributeError
: Raised when an object does not have an expected attribute.IOError
: Raised when an input/output operation fails.ImportError
: Raised when an import statement fails.RuntimeError
: Raised when an error that does not belong to any other category occurs.
These exceptions can be caught and handled using try-except
blocks in Python. By catching and handling these exceptions, we can make our code more robust and prevent it from crashing due to errors.
Different Real-World Use Cases of Exception Handling
Safely Operating With Numbers
In the previous example, we saw how to handle the ZeroDivisionError
exception that occurs when we try to divide a number by zero:
numerator = 10
denominator = 0
try:
result = numerator / denominator
except ZeroDivisionError:
print('Error: Division by zero!')
In this code, we try to divide numerator
by denominator
. Since denominator
is zero, a ZeroDivisionError
exception is raised. We catch the exception using a try-except
block and print an error message. Of course, in real life, the values for numerator
and denominator
are not fixed, and can depend on the user input. Which makes handling error cases even more vital.
Working With Files Safely
Python provides a way to read and write files using the built-in open()
function. However, if the file does not exist, Python raises a FileNotFoundError
exception:
try:
with open('myfile.txt', 'r') as file:
contents = file.read()
except FileNotFoundError:
print('Error: File not found!')
In this code, we try to open a file called myfile.txt
for reading. If the file does not exist, a FileNotFoundError
exception is raised. We catch the exception using a try-except
block and print an error message.
User Input Validation
When working with user input, it’s essential to validate the input to prevent errors:
try:
age = int(input('Enter your age:'))
except ValueError:
print('Error: Invalid input!')
else:
if age < 0:
print('Error: Age cannot be negative!')
else:
print('Your age is:', age)
In this code, we ask the user to enter their age. We use the int()
function to convert the user's input to an integer. If the user enters an invalid input, such as a string or a floating-point number, a ValueError
exception is raised. We catch the exception using a try-except
block and print an error message. If the input is valid, we check if the age is negative and print an error message if it is. Otherwise, we print the age.
Network Errors
When working with network connections, it’s important to handle exceptions that may occur due to network issues:
import requests
url = 'https://www.google.com'
try:
response = requests.get(url)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print('Error: ', e)
else:
print('Response code:', response.status_code)
In this code, we use the requests
module to send a GET request to the Google website. If the request fails due to a network error, a RequestException
exception is raised. We catch the exception using a try-except
block and print an error message. If the request is successful, we print the response code.
Best Practices for Handling Exceptions
Here are some best practices for handling exceptions in Python:
- Be specific with your exception handling: Catch specific exceptions rather than using a broad
except
statement. This allows you to handle specific errors in a more targeted manner. - Keep exception-handling concise: Try to keep your exception-handling blocks as short and concise as possible. This makes your code more readable and easier to debug.
- Log exceptions: Instead of printing error messages, use Python’s built-in
logging
module to log exceptions. This allows you to easily track and debug errors in your code. - Use
finally
blocks for cleanup: If your code needs to perform some cleanup tasks, such as closing files or database connections, use afinally
block to ensure that the cleanup code is always executed.
What’s next?
- If you found this story valuable, please consider clapping multiple times (this really helps a lot!)
- Hands-on Practice: Free Python Course
- Full series: 100 Days of Python
- Previous topic: Class Decorators
- Next topic: Exception Hierarchy