Skip to Content
Course content

141: The else and finally Clauses

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you're comfortable with try and except. You know how to stop your program from crashing when something goes sideways. But in real-world production code, just "not crashing" isn't enough. We often have to deal with cleanup—closing database connections, releasing file locks, or logging the successful completion of a risky operation.

That's where else and finally come in. They aren't used in every single try-except block, but when you need them, they're indispensable for writing clean, predictable code.

Cleaning up with finally

Let's build a small utility that reads a sensitive configuration file. The most important thing here is that regardless of whether the read succeeds or fails, we must close the file handle to avoid memory leaks or locking the file from other processes.

def read_config(filepath):
    file = open(filepath, 'r')
    try:
        data = file.read()
        print("File read successfully.")
    except IOError:
        print("Couldn't read the file!")
    finally:
        print("Closing the file handle now.")
        file.close()

read_config("settings.txt")

The finally block is the "no matter what" clause. If the code in the try block works, finally runs. If an except block catches an error, finally still runs. Even if the try block contains a return statement, Python will actually execute the finally block before the function officially returns. It's your safety net for resource management.

The mistake of overloading the try block

Now, I want to add some logic to process the data we just read. My first instinct—and a mistake I see a lot of developers make—is to just throw everything into the try block. Let's look at this version:

def process_config(filepath):
    file = open(filepath, 'r')
    try:
        data = file.read()
        # I'm assuming the config is a number; let's convert it
        value = int(data.strip()) 
        print(f"Configuration value is: {value}")
    except IOError:
        print("File system error occurred.")
    finally:
        file.close()

Here is the problem: if data.strip() isn't a number, Python raises a ValueError. But since that's inside the try block, and I'm only catching IOError, the program will still crash. Worse, if I had used a generic except Exception:, I would have caught the ValueError and told the user "File system error occurred," which is a lie. The file system was fine; the data was bad.

Separating risk with the else clause

This is exactly why the else clause exists. The else block runs only if the try block completed without raising any exceptions. It allows us to isolate the "risky" code (opening the file) from the "processing" code (converting the data).

Let's rewrite this the right way:

def process_config_correctly(filepath):
    try:
        file = open(filepath, 'r')
    except IOError:
        print("Could not open the file.")
        return # Exit early if we can't even get the file

    try:
        data = file.read()
    except IOError:
        print("Error reading file content.")
    else:
        # This only runs if the read succeeded.
        # If int() fails here, it raises a ValueError that 
        # isn't confused with a file system error.
        value = int(data.strip())
        print(f"Configuration value is: {value}")
    finally:
        print("Cleaning up resources...")
        file.close()

I've separated the concerns. The try block is now surgically focused on the operation that is likely to fail due to external factors. The else block handles the logic that depends on that success. If the int() conversion fails, the program crashes (or is caught by a higher-level handler) with the correct error type, and finally still ensures the file is closed. This makes debugging significantly easier because the traceback points to the actual source of the problem, not just a generic "catch-all" block.




📋 Practical Task

Exercise: Building a Robust Network Socket Mock

You are simulating a network connection that reads a message from a remote server. You need to write a function called fetch_server_message(connection_status) that mimics this behavior.

Requirements:

  • The function should start by simulating a connection. If connection_status is "down", raise a ConnectionError.
  • Wrap the connection attempt in a try block.
  • Use an except ConnectionError block to print "Network unreachable."
  • Use an else block to simulate receiving a message. In this block, attempt to decode the message: if the message is "CORRUPT", raise a ValueError. If it's anything else, print "Message received: [message]".
  • Use a finally block to print "Closing network socket..." regardless of whether the connection succeeded, the network was down, or the data was corrupt.

Test your function with these three scenarios:

  1. fetch_server_message("down") → (Should print network error and close socket)
  2. fetch_server_message("CORRUPT") → (Should raise ValueError and still close socket)
  3. fetch_server_message("Hello World") → (Should print the message and close socket)
Rating
0 0

There are no comments for now.

to be the first to leave a comment.