Articles

Exception & Error Handling in Python

Published Mar 19, 2025Updated Mar 20, 2025
Learn how to handle Python exceptions using try-except blocks, avoid crashes, and manage errors efficiently. Explore Python error-handling techniques, including built-in exceptions, custom exceptions, and best practices.

Introduction to exceptions and errors in Python

Errors are inevitable in programming, but how we handle them determines the reliability and user experience of our applications.

Imagine you’re shopping on an e-commerce site, ready to check out, but something goes wrong with the payment process, and suddenly, the entire site crashes. Instead of crashing the whole site, what if the system could gracefully handle the issue without causing any disruption? Error handling in programming allows us to handle such issues without crashing the entire system.

In Python, we can gracefully manage errors, ensuring the program doesn’t halt even if something goes wrong.

In this article, we will explore the various types of errors in Python that can occur during program execution, how to handle them, and how to define and raise custom exceptions for specific error conditions.

Let’s start by exploring the different types of errors in Python.

Related Course

Learn JavaScript: Error Handling

Learn how to create more secure applications by predicting, handling, and debugging errors with JavaScript.Try it for free

Types of errors in Python

Python categorizes errors into three main types:

1. Syntax errors

These errors arise when the code violates Python’s syntax rules. The interpreter usually points them out during compilation, making them easy to spot.

For example:

print("Hello, world!')

In this case, the mismatched quotation marks result in a syntax error.

2. Runtime errors

These errors happen during the execution of the program, often due to issues like invalid input or division by zero.

For example:

result =10/0

This code causes a ZeroDivisionError at runtime when attempting to divide by zero.

3. Logical errors

They are the hardest to detect. When this error occurs, the code runs without crashing, but the output is incorrect due to flawed logic.

For example:

defcalculate_area(radius):
return radius *2# Incorrect formula, should be radius**2 * pi
area = calculate_area(5)
print(area)# Incorrect area calculation

In this code, the formula for calculating the area of a circle is wrong, leading to an incorrect result despite the program running without errors.

Common built-in exceptions in Python

Python also has some built-in exceptions:

1. ValueError

Raised when a function is given an argument of the correct type but with an invalid or inappropriate value.

For example, converting a string to an integer when the string does not represent a number will result in a ValueError.

int("abc")# Raises ValueError

2. TypeError

Raised when an operation or function is used on an object of an incompatible type.

For example, adding a string to an integer will result in a TypeError.

"Hello"+5# Raises TypeError

3. KeyError

Raised when attempting to access a key in a dictionary that is not present.

For example, trying to access the value of key b that does not exist in the dictionary raises a KeyError.

my_dict ={"a":1}
print(my_dict["b"])# Raises KeyError

4. IndexError

Raised when trying to access an index in a list or tuple that is out of range.

For example, attempting to access the element at index 5 in a list with only 3 elements will result in an IndexError because the specified index is out of the valid range (0 to 2 for this list).

my_list =[1,2,3]
print(my_list[5])# Raises IndexError

5. ZeroDivisionError

Raised when dividing a number by zero.

For example, dividing any number by zero, as shown in the code snippet, will raise a ZeroDivisionError because division by zero is undefined in mathematics.

result =10/0# Raises ZeroDivisionError

6. FileNotFoundError

Raised when attempting to open a file that does not exist.

For example, trying to open a file that does not exist, as in the code snippet, will raise a FileNotFoundError because the specified file cannot be found in the directory.

open("nonexistent_file.txt","r")# Raises FileNotFoundError

7. IOError

Raised when an input/output operation fails.

For example, attempting to write to a file opened in read-only mode, as shown in the code snippet, will raise an IOError because the operation is not permitted.

withopen("read_only_file.txt","r")asfile:
file.write("data")# Raises IOError

8. AttributeError

Raised when an invalid attribute is accessed on an object.

For example, trying to call a non-existent method like sort_items() on a list, as shown in the code snippet, will raise an AttributeError because the list object has no such attribute.

my_list =[1,2,3]
my_list.append(4)
my_list.sort_items()# Raises AttributeError

9. NameError

Raised when a variable or function is referenced before being defined.

For example, referencing a variable that has not been defined, as shown in the code snippet, will raise a NameError because the variable does not exist in the current scope.

print(undefined_variable)# Raises NameError

10. ImportError

Raised when an import statement fails to find the module or when an object cannot be imported from a module.

For example, attempting to import an object that does not exist in a module, as shown in the code snippet, will raise an ImportError because the specified object cannot be found.

from math import unknown_function # Raises ImportError

11. RuntimeError

Raised for generic errors that don’t fit into any other category.

For example, explicitly raising a RuntimeError with a custom error message, as in the code snippet, results in a RuntimeError, typically used for general-purpose errors.

raise RuntimeError("This is a runtime error.")# Raises RuntimeError

12. AssertionError

Raised when an assert statement fails.

For example, if the condition in an assert statement evaluates to False, as shown in the code snippet, it raises an AssertionError along with the specified error message.

x =10
assert x >20,"x is not greater than 20"# Raises AssertionError

Now that we know the types of errors and exceptions that can occur in a Python program let’s explore how Python allows us to manage them using try and except blocks.

Using try and except to handle errors

Python provides a straightforward and effective way to handle errors using the try and except blocks. These blocks allow the program to continue running even when an error occurs.

Example 1: Handling a ZeroDivisionError

Consider a scenario where we divide a number by zero, which will cause a ZeroDivisionError. Without error handling, the program would crash. However, with try and except, we can catch the error and prevent the program from stopping like this:

try:
result =10/0
except ZeroDivisionError:
print("Cannot divide by zero!")

In this case, the program tries to execute the code in the try block. When it encounters a ZeroDivisionError, the except block catches the error and prints a friendly message, allowing the program to continue instead of crashing.

This error-handling approach is essential for ensuring smooth execution, mainly when unexpected input or conditions may cause the program to fail.

Example 2: Handling multiple exceptions

What if multiple errors can happen in a single block of code? How do we handle different scenarios without writing multiple try blocks? Python also allows us to handle multiple exceptions in a single try-except block like this:

try:
value =int(input("Enter a number: "))
result =10/ value
except ValueError:
print("That's not a valid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")

In this example, the program can easily handle both ValueError (if the user doesn’t enter a number) and ZeroDivisionError (if the user enters zero) in the same code block. Each exception type is handled by its respective except clause, providing tailored error messages for the user.

Now that we’ve covered how to handle errors using try and except, let’s explore how we can enhance our error-handling approach using the else and finally clauses.

Using else and finally clauses

The else and finally clauses give you additional control over the execution flow. They allow you to run code when no errors occur and ensure that certain operations execute regardless of whether an error occurs.

The else block runs when no exceptions occur during the try block.

The finally block runs no matter what, making it useful for cleanup operations (e.g., closing files or releasing resources).

Here’s how we can use try-except-else-finally:

try:
result =10/2
except ZeroDivisionError:
print("Cannot divide by zero!")
else:
print("Division successful, result is:", result)
finally:
print("Execution complete, cleanup operations can go here.")

In this case:

  1. The else block executes if no exception occurs (division is successful).
  2. The finally block runs regardless of whether an error occurred and ensures any necessary cleanup.

While Python provides built-in exceptions for common errors, there are cases where unique application requirements demand specific handling. This is where custom exceptions come into play.

Raising custom exceptions

Custom exceptions can make error handling more intuitive by providing clear messages that reflect the problem’s context instead of using generic exceptions.

In Python, we can use the raise keyword to define custom exceptions to handle unique situations specific to our application that the built-in exceptions may not cover.

Example: Creating and raising a custom exception

Suppose we are building an application where users can register their age. If a user enters a negative age, we can raise a custom exception to handle this error like this:

classInvalidAgeError(Exception):
"""Custom exception for invalid age input."""
pass
defset_age(age):
if age <0:
raise InvalidAgeError("Age cannot be negative.")
print(f"Age is set to {age}")
try:
set_age(-5)
except InvalidAgeError as e:
print(f"Error: {e}")

We can break down the execution of this code into the following steps:

  1. Defining Custom Exception (InvalidAgeError): We define a custom exception class InvalidAgeError, inheriting from Python’s built-in Exception class.

  2. Raising the Exception: Inside the set_age function, we check if the age is negative. If so, we raise the InvalidAgeError with a specific error message.

  3. Handling the Exception: In the try block, we attempt to call set_age with an invalid age (-5). The except block catches the InvalidAgeError and prints the custom error message.

Now that we’ve covered how to define and raise custom exceptions, it’s important to consider best practices for exception handling. Properly managing exceptions can improve the reliability and maintainability of your code.

Let’s explore some key guidelines when implementing error handling in Python.

Best practices for exception handling

Handling exceptions thoughtfully and precisely can significantly improve program flow and error resolution. When implementing exception handling in Python, there are several best practices to keep in mind:

1. Catch specific exceptions

Avoid using a generic except block that catches all exceptions. Catching specific exceptions allows for better control and more precise error handling. This practice makes your code more predictable and prevents unintentionally catching unrelated errors.

For example,

try:
value =int(input("Enter a number: "))
except ValueError:
print("That's not a valid number!")

In this example, we only catch the ValueError if the user inputs a non-integer. This prevents other exceptions from being caught, allowing more specific responses to different error types.

2. Avoid overusing exceptions for flow control

Exceptions should handle errors, not dictate the program’s flow. Using exceptions for flow control leads to inefficient and hard-to-maintain code.

Here is an example:

try:
ifnot some_condition:
raise Exception("Flow control issue.")
except Exception:
pass# Not a valid use of exceptions.

It’s better to use conditional statements to handle normal flow and reserve exceptions for actual error cases.

3. Use finally for cleanup

The finally block runs code that must always execute, regardless of whether an exception occurred. It is ideal for cleanup tasks like closing files or releasing resources.

For example,

try:
file=open("example.txt","r")
data =file.read()
except IOError:
print("Error reading file.")
finally:
file.close()# Always closes the file, even if an error occurred.

In this example, file.close() is placed in the finally block to ensure the file is always closed, whether or not an error occurs. Closing the file in the finally block is necessary to prevent resource leaks.

4. Log errors for debugging

Logging errors helps debug and provides a record of issues during runtime. In production environments, logging can give insights into problems without interrupting the user experience.

logging.basicConfig(level=logging.ERROR)
try:
result =10/0
except ZeroDivisionError as e:
logging.error(f"Error occurred: {e}")

Here, the logging module captures the error message in a log file or console, making it easier to debug issues later. Logging is often preferred over printing error messages directly because it allows for more advanced features like logging levels, timestamps, and persistent logs.

5. Provide meaningful error messages

When raising exceptions or logging errors, make sure the custom error messages clearly describe the issue. This will help developers understand and fix the problem.

For example, this custom error message clearly states that the age must be a positive number:

raise ValueError("Age must be a positive number.")

Conclusion

Exception handling is essential for making Python programs more reliable. We can handle errors without stopping the program by using try, except, else, and finally. Creating custom exceptions gives you more precise error messages, and following best practices, like catching and documenting specific errors, helps keep your code clean and easy to maintain. Using these techniques will help you build stronger programs that handle errors smoothly.

Applying these concepts in real-world projects will equip you to manage errors and build robust programs. To learn more Python concepts, check out Learn Python 3 course by Codecademy.

Codecademy Team

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

Meet the full team
close