Python — Exception Handling (57/100 Days of Python)

Martin Mirakyan
6 min readFeb 27, 2023
Day 57 of the “100 Days of Python” blog post series covering exceptions and exception handling

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:

  1. TypeError: Raised when an operation or function is applied to an object of inappropriate type.
  2. ValueError: Raised when an operation or function is applied to an object with the correct type, but with an inappropriate value.
  3. IndexError: Raised when an index is out of range.
  4. KeyError: Raised when a key is not found in a dictionary.
  5. NameError: Raised when a variable or name is not defined.
  6. ZeroDivisionError: Raised when division by zero occurs.
  7. FileNotFoundError: Raised when a file or directory is not found.
  8. AssertionError: Raised when an assertion fails.
  9. OverflowError: Raised when a mathematical operation overflows.
  10. SyntaxError: Raised when there is a syntax error in the code.
  11. AttributeError: Raised when an object does not have an expected attribute.
  12. IOError: Raised when an input/output operation fails.
  13. ImportError: Raised when an import statement fails.
  14. 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:

  1. 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.
  2. 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.
  3. 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.
  4. Use finally blocks for cleanup: If your code needs to perform some cleanup tasks, such as closing files or database connections, use a finally block to ensure that the cleanup code is always executed.

What’s next?

--

--

Martin Mirakyan

Software Engineer | Machine Learning | Founder of Profound Academy (https://profound.academy)