Exception & Error Handling in Python
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.
Learn JavaScript: Error Handling
Learn how to create more secure applications by predicting, handling, and debugging errors with JavaScript.Try it for freeTypes 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 * piarea = 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 =10assert 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/0except 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/ valueexcept 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/2except 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:
- The
else
block executes if no exception occurs (division is successful). - 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."""passdefset_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:
Defining Custom Exception (
InvalidAgeError
): We define a custom exception classInvalidAgeError
, inheriting from Python’s built-inException
class.Raising the Exception: Inside the
set_age
function, we check if the age is negative. If so, we raise theInvalidAgeError
with a specific error message.Handling the Exception: In the
try
block, we attempt to callset_age
with an invalid age (-5). The except block catches theInvalidAgeError
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/0except 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.
'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 teamRelated articles
- Article
Errors in C++
Errors are simply unavoidable when you develop a program, let's learn more about them! - Article
Transforming Code Errors into Learning Opportunities
Discover how to view coding errors as valuable learning experiences in our comprehensive guide. Learn effective strategies for identifying, analyzing, and fixing errors in your code. - Article
Python Syntax Guide for Beginners
Learn Python syntax with this beginner-friendly guide. Understand Python indentation, print statements, variables, comments, user input, and more with examples.
Learn more on Codecademy
- Free course
Learn JavaScript: Error Handling
Learn how to create more secure applications by predicting, handling, and debugging errors with JavaScript.Beginner Friendly1 hour - Free course
Learn Intermediate Python 3: Exceptions and Unit Testing
Learn to maintain a healthy codebase by creating unit tests using Python's built-in `unittest` framework.Intermediate4 hours - Course
Learn Intermediate Go: Effective Error Handling
Learn about the best practices for effective error handling in Go.With CertificateIntermediate2 hours