Guide to Exception Handling in Python
Getting Started
As Python programmers, we'll inevitably stumble upon errors. Roughly, there are two types of errors; syntax errors and exceptions. The first stem from syntax blunders or other oversights in our code, while exceptions are those pesky messages that pop up during program execution.
For us to handle these mishaps and ensure the program runs smoothly, we'll need to include exception handling in our toolkit.
Syntax Errors
Sometimes, we refer to syntax errors as parsing ones and they arise when the Python interpreter stumbles upon a line of code that it can't make sense of. These blunders often result from typos, incorrect syntax, or other mistakes.
print("Hello, world!')
# SyntaxError: unterminated string literal (detected at line 1)
So, the syntax error here is because we didn't match the quotations type properly. While it may seem trivial, a tiny mistake such as misspelling or missing symbols can bring our code to a halt.
Logical Errors
Logical errors are subtle and may slip through when our code runs without any syntax or runtime errors, yet the output isn't quite what we anticipated. Since they don't cause your program to crash or raise an exception, logical errors can be notoriously elusive. Take a look at the following example:
def percentage(part, total):
return part / total * 100
score = 45
max_score = 50
result = percentage(max_score, score)
print(f"The percentage is {result}%")
# Output: The percentage is 111.11111111111111%
In this case, the logical error lies in the incorrect order of arguments when we call the percentage function. We should be passing score
as the first argument and max_score
as the second. Instead, we have them reversed, leading to an inaccurate percentage calculation.
Runtime Errors
Runtime exceptions crop up during program execution when something unforeseen goes wrong. These issues aren't detectable by the Python interpreter during the compilation phase and can be caused by a variety of factors, such as invalid input data or network failures.
num1 = 10
num2 = 0
result = num1 / num2
print(result)
# ZeroDivisionError: division by zero
So, here the error is raised because we're attempting to divide by zero, which is mathematically undefined. Python catches and informs us about this type of runtime exception during the program execution.
The Try-except Block
Now, let's focus on handling exceptions using the try-except
block. With this approach, we can catch exceptions and address them in an orderly fashion.
try:
# Code that might raise an exception
except ExceptionType:
# Code to handle the exception
First, we place the code that might raise an exception within the try block. Then, in the accompanying except block, we can specify the type of exception we want to catch, along with the code to handle that particular exception.
try:
num1 = int(input("Enter a number: "))
num2 = int(input("Enter another number: "))
result = num1 / num2
print(result)
except ZeroDivisionError:
print("You can't divide by zero!")
except ValueError:
print("Invalid input. Please enter a valid integer.")
The code inside the try block prompts the user to input two numbers, performs a division operation, and displays the result. If our user enters a zero for the second number, a ZeroDivisionError
is triggered. Or, if he enters a non-numeric value, a ValueError
shows up. The code within the except blocks neatly captures these exceptions and prints suitable error messages for them.
Keep in mind that we can use multiple except blocks to handle various types of exceptions. If an exception arises that doesn't align with any of the designated except blocks, the program will terminate, accompanied by an error message we provide.
The Finally Block
The finally
block serves a crucial purpose in exception handling — we can ensure that specific code segments execute, irrespective of whether an exception is raised. Note that this feature can be particularly handy when we need to close files, release resources, or perform any necessary cleanup tasks.
try:
# Code that might raise an exception
except ExceptionType:
# Code to handle the exception
finally:
# Code to execute regardless of whether an exception was raised
Let's see it in action.
try:
file = open("data.txt", "r")
data = file.read()
print(data)
except FileNotFoundError:
print("File not found.")
finally:
file.close()
So, in the try block, we aim to open a file, read its contents, and then display them. If the file is nowhere to be found, a FileNotFoundError
is triggered. Despite the presence or absence of an exception, the finally block ensures that the file is closed, maintaining a clean, orderly program execution.
Raising Exceptions
Beyond handling exceptions raised by Python itself, we can create and raise our own with the raise
statement.
raise ExceptionType("Error message")
Let's explore this concept with a different example.
def greet(name):
if not name:
raise ValueError("Name cannot be empty.")
return f"Hello, {name}!"
try:
message = greet("")
except ValueError as error:
print(error)
Initially, the greet function raises a ValueError
if the provided name is empty. Below, we can observe the code within the try block that calls the greet
function with an empty string, triggering the exception.
Lastly, the except block catches the exception and prints the corresponding error message. By raising custom exceptions we can better handle specific scenarios and maintain control over the execution flow.
Custom Exceptions
There may be occasions when you need to create your exceptions to manage specific error conditions in your code. This can be achieved by defining a new class that inherits from the Exception class.
class CustomException(Exception):
pass
Let's examine a practical example.
class InvalidInputError(Exception):
def __init__(self, message):
self.message = message
try:
age = int(input("Enter your age: "))
if age < 0:
raise InvalidInputError("Age cannot be negative.")
except InvalidInputError as error:
print(error.message)
We defined the InvalidInputError
class to tackle situations where users input a negative age. The try block prompts the user to enter their age; if a negative age is provided, it raises an instance of the class. Moreover, the except block subsequently catches the exception and displays the associated error message. Logically, this demonstrates how custom exceptions can help us address unique error scenarios and maintain greater control over the execution.
Final Thoughts
Undoubtedly, exception handling can be a difference maker. It enables us to manage errors and maintain smooth program execution, even when unexpected issues arise.
We explored various types of exceptions in Python and discussed handling them using try-except blocks, utilizing the finally block, raising exceptions, and even creating custom ones.
Don't forget to check out the useful resources below to expand your knowledge and strengthen your Python skills. And remember, it never hurts to revisit some of the basic Python concepts included in the examples throughout this article.
Useful Resources
Offical Python Documentation on Errors and Exceptions