Lars Cornelissen


Error Handling in Python: Try, Except, and Finally Blocks

Profile Picture Lars Cornelissen
Lars Cornelissen • Follow
CEO at Datastudy.nl, Data Engineer at Alliander N.V.

4 min read


text

Introduction to Error Handling

Error handling is like the underrated superhero of the coding world. Most newbies to programming, including myself once upon a time, often overlook it or underestimate its importance. Let me put it this way: ignoring error handling is like ignoring a leaky tap—sooner or later, you're going to end up with a flooded kitchen.

When I first started coding, I thought that writing code meant just making things work. But over time, I realized that writing robust code means expecting things to break and planning for those breakages. Trust me, it's not as scary as it sounds!

So what exactly is error handling? In simplest terms, it’s the process of responding to and managing errors that occur while your program is running. Errors can range from simple issues like a missing file to more complex ones like failed database connections or invalid user input.

To make things easier, think of error handling in two categories:

  1. Synchronous Errors: These errors are predictable and can be caught during the development phase. They're like that one friend who always shows up late—you know they're going to do it, so you plan accordingly.
  2. Asynchronous Errors: These errors happen unexpectedly and usually occur during runtime, making them similar to surprise party crashes. They can be tricky but can still be managed with forethought and the right approach.

Rust, for example, has become quite popular due to its robust error handling mechanisms. Rust uses the Result and Option types to handle errors gracefully. When you call a function that can fail, it returns a Result type, which can be either an Ok or an Err value.

Here's a quick example:

fn divide(dividend: f64, divisor: f64) -> Result<f64, String> {
    if divisor == 0.0 {
        Err(String::from("Divide by zero error"))
    } else {
        Ok(dividend / divisor)
    }
}

In this function, if the divisor is zero, it returns an error. Otherwise, it returns the quotient. Simple yet effective!

Let's look at a real-world situation. Suppose you're writing a script to read a file and process its contents. Without proper error handling, your script might crash if the file doesn't exist, or worse, it could produce incorrect results without any indication that something went wrong. With error handling, you can gracefully inform the user, log the error, and even attempt to correct it or retry the operation.

Here's a quick checklist for effective error handling

1. Understand the types of errors your code might encounter. 2. Use specific error messages and codes. 3. Log errors for future debugging. 4. Test your error paths as rigorously as your normal paths. 5. Inform the user in a clear and constructive manner.

Believe me, error handling will save you a lot of headaches. And if you're anything like me, you'll even start to enjoy the process! Think of it as adding a safety net to your coding acrobatics.

Happy error handling, and may your bugs be ever in your favor!

Using Try and Except Blocks

If you've ever found yourself scratching your head over an error message that popped up in your Python code, you're not alone. Enter the magical world of try and except blocks. These handy constructs can save your code from crashing and burning whenever an unexpected error decides to rear its ugly head.

At its core, a try block lets you test a block of code for errors, while the except block lets you handle the error if one occurs. Think of it like this: try is the adventurous one who takes risks, and except is the cautious friend who waits with a first-aid kit.

Here's a simple example to help you get started:

try:
    x = 1 / 0
except ZeroDivisionError:
    print("Oops! You can't divide by zero!")

In this snippet, I attempted to divide one by zero, which we all know is a no-go. Fortunately, the except block catches the ZeroDivisionError and prints out a friendly message instead of letting the program crash.

The beauty of try and except is that you can catch specific exceptions or go broad. Specific catches are more precise, but sometimes you just need to grab everything, and that's okay—like when you try to catch everything falling off a shelf. Here's how it looks:

try:
    print(nonexistent_variable)
except NameError:
    print("Caught a NameError!")
except:
    print("Caught an unexpected error!")

In this example, you’ll first catch a NameError if such an error occurs. If any other error happens, it falls into the generic except block.

It's always good practice to be as specific as possible with your exceptions. It makes debugging a lot easier. Trust me, figuring out why your code is playing dead is hard enough without a mysterious rogue exception floating around.

But wait, there's more! You could also use the else and finally clauses to make your error handling more robust. But let’s not try to juggle too many balls at once. We’ll save those gems for another chapter.

One last thing before I let you start poking around your code like a detective: using multiple try-except blocks can keep your code clean and efficient. For instance:

try:
    result = 10 / 2
    print(result)
except ZeroDivisionError:
    print("Dividing by zero is a bad idea!")

try:
    print(non_existent)
except NameError:
    print("Oops, variable doesn't exist!")

It's like having multiple pairs of eyes, each focused on a different part of your code. This way, one error won’t cause the whole house of cards to collapse.

In my first attempts at using try and except, I felt like a kid trying to ride a bike without training wheels. But over time, it became second nature, and I promise it will for you too. Now go ahead, put on your coding helmet and give it a try!

Finally Block: Ensuring Cleanup

Have you ever left a room and forgot to turn off the lights? Or finished cooking and left all the dirty dishes in the sink? In programming, when we deal with resources like files, network connections, or databases, it's crucial to clean up after ourselves, even if an error occurs. The finally block is here to help us with that.

The finally block is a feature in many programming languages, including Python, that allows us to specify a section of code that will be executed no matter what happens in the try or except blocks. Think of it as a cleanup crew that comes in after the party is over, regardless of how wild things got.

Let's see an example of how the finally block works:

try:
    file = open('example.txt', 'r')
    content = file.read()
    # Process the content
except FileNotFoundError:
    print('The file was not found.')
finally:
    file.close()
    print('File closed.')

In this example, we try to open and read from a file. If the file is not found, a FileNotFoundError is raised and caught in the except block. Regardless of whether an error occurs, the finally block ensures that the file is closed, preventing resource leakage.

Why is this important? Well, if we forget to close a file or release a resource, it can lead to resource depletion, memory leaks, or other unintended side effects. The finally block guarantees that our cleanup code runs every time.

Another common use of the finally block is when working with network connections or database transactions. Here's a more detailed example involving a database connection:

import sqlite3

try:
    conn = sqlite3.connect('example.db')
    cursor = conn.cursor()
    # Execute some database operations
except sqlite3.DatabaseError:
    print('Database error occurred.')
finally:
    conn.close()
    print('Database connection closed.')

In this case, we connect to a database and execute some operations. If a database error occurs, it's caught in the except block, but the finally block ensures that the database connection is always closed properly.

It's important to note that the finally block doesn't handle exceptions. Its sole purpose is to provide a place for cleanup code that must run no matter what. If an exception is raised in the try block and not caught in an except block, the finally block will still execute, but the exception will propagate after the finally block runs.

For completeness, here's how control flows with try, except, and finally:

Situation try except finally
No exception raised Yes No Yes
Exception raised, then caught Yes Yes Yes
Exception raised, not caught Yes No Yes

Understanding how and when to use the finally block can make your code more robust and resilient. It's like having a safety net that ensures, no matter what happens, things are left in a good state. Next time you need to clean up after your code, remember the trusty finally block. And hey, at least code never leaves dirty dishes behind!


Python

Error Handling

Programming

Code

Developer Tips