Python Programming for Applied Projects
Error Handling
Handling the Unexpected
Even the best-written code can run into problems. Sometimes, the issue isn't with your logic but with unexpected input or situations. For example, what happens if your program asks a user for their age and they type "ten" instead of 10? Or what if you try to read a file that doesn't exist? Without a plan, these situations will crash your program.
In Python, these runtime errors are called exceptions. When an error occurs during execution, Python raises an exception, and the program stops. Let's look at a classic example: division by zero.
numerator = 10
denominator = 0
result = numerator / denominator
print("This line will never be reached.")
If you run this code, it will halt and display a ZeroDivisionError. The program never gets to the print statement. To prevent this, we need a way to catch these exceptions and handle them gracefully.
The Try and Except Block
Python provides a simple but powerful tool for handling exceptions: the try and except block. The logic is straightforward: you put the code that might cause an error inside the try block. Then, you tell Python what to do if a specific error occurs in the except block.
It’s like telling your program, “Try to do this, but if you run into this specific problem, do this other thing instead and keep going.”
Let's fix our division-by-zero example. By wrapping the risky operation in a try block, we can catch the ZeroDivisionError and print a friendly message instead of crashing.
numerator = 10
denominator = 0
try:
result = numerator / denominator
print(result)
except ZeroDivisionError:
print("Oops! You can't divide a number by zero.")
print("The program continued without crashing!")
Now, the program doesn't crash. It executes the code in the except block and then continues on to the final print statement. Another common issue is the ValueError, which occurs when an operation receives an argument of the correct type but an inappropriate value. A classic case is trying to convert a non-numeric string to an integer.
user_input = "cat"
try:
number = int(user_input)
print(f"The number is {number}")
except ValueError:
print(f"'{user_input}' is not a valid number.")
Handling Multiple Exceptions
Sometimes, a single block of code can raise more than one type of exception. You can handle this in a couple of ways.
First, you can list multiple exception types in a single except block by putting them inside a tuple. This is useful when you want to perform the same action for several different errors.
my_list = [10, 20, 30]
try:
index = int(input("Enter an index: "))
print(my_list[index] / 0)
except (ValueError, IndexError):
print("That's not a valid index. Please enter a number.")
except ZeroDivisionError:
print("You tried to divide by zero!")
Alternatively, you can have multiple except blocks, one for each specific exception you want to handle. This lets you run different code for different errors. Python will check each except block in order and execute the first one that matches the error.
try:
number_string = "20"
result = int(number_string) / 0 # This will cause a ZeroDivisionError
except ValueError:
print("Could not convert the string to a number.")
except ZeroDivisionError:
print("Cannot divide by zero. That's a math rule!")
except:
print("An unknown error occurred.")
Notice the final except block has no specific exception type. This is a catch-all that will handle any error not caught by the preceding blocks. It's good practice to use this sparingly and place it at the end, as it can sometimes hide bugs you didn't anticipate.
The Else and Finally Clauses
The try block has two more optional clauses: else and finally. They give you even more control over your program's flow.
The else clause lets you run a block of code only if the try block completes successfully without raising any exceptions. This is useful for code that should only run when the risky part succeeds.
try:
num = int(input("Enter a number: "))
except ValueError:
print("That wasn't a number.")
else:
print(f"You entered the number {num}.")
The finally clause is for code that must run no matter what, whether an exception occurred or not. It's often used for cleanup operations, like closing a file or releasing a network connection, ensuring these actions happen even if the program runs into an error.
try:
f = open("my_file.txt", "w")
f.write("Hello, world!")
# Uncomment the line below to see an error happen
# result = 10 / 0
except:
print("Something went wrong!")
finally:
print("Closing the file.")
f.close()
In this example, the message "Closing the file." will always be printed, and f.close() will always be called. This guarantees that the file is properly closed, preventing potential data corruption.
Exception handling (try, except, finally) enables graceful handling of runtime errors and exceptions.
By mastering try, except, else, and finally, you can build more resilient and user-friendly programs that don't just crash when the unexpected happens.
What is the primary purpose of a try...except block in Python?
Consider the following code snippet. What will be the output?
my_list = [10, 20, 30]
result = 0
try:
result = my_list[1] / 0
except ValueError:
print("A ValueError occurred")
except ZeroDivisionError:
print("Cannot divide by zero!")
except:
print("An unknown error occurred")
finally:
print("Execution finished.")