Practical Example: Closing Connections and Files

🏷️ Error Handling and Exceptions / Else and Finally Clauses


🔍 Context Introduction

When working with external resources like files, database connections, or network sockets, it is critical to ensure they are properly closed after use—even if an error occurs. Leaving connections or files open can lead to resource leaks, corrupted data, or system instability. Python's finally clause provides a reliable way to execute cleanup code regardless of whether an exception was raised, making it an essential tool for writing robust and production-ready code.


⚙️ Why Closing Resources Matters

  • File handles are limited system resources. Leaving files open can exhaust the available handles and cause other parts of your application to fail.
  • Database connections left open can lock tables, consume memory, and prevent other users from accessing data.
  • Network sockets that are not closed properly can lead to connection timeouts and unpredictable behavior.
  • The finally clause guarantees that your cleanup code runs, whether the code block succeeds or fails with an exception.

🛠️ Basic Pattern: Try-Except-Finally

The standard pattern for safely managing resources follows this flow:

  • try block: Contains the code that might raise an exception (e.g., opening a file or connecting to a database).
  • except block: Handles any exceptions that occur, allowing you to log the error or take corrective action.
  • finally block: Always executes, providing a safe place to close files, release connections, or free other resources.

Example structure: - Open a file for reading inside the try block. - If the file does not exist, the except block catches the FileNotFoundError and prints a meaningful message. - The finally block checks if the file object was created and closes it, preventing a resource leak.


📊 Comparison: With vs. Without Finally

Aspect Without Finally With Finally
Resource cleanup Must be manually repeated in both try and except blocks Written once in the finally block
Risk of forgetting High—easy to miss cleanup in one code path Low—cleanup is guaranteed
Code duplication High—same close logic appears multiple times Low—single cleanup location
Handling unexpected exceptions Cleanup is skipped if an unhandled exception occurs Cleanup always runs, even for unhandled exceptions
Readability More cluttered and error-prone Cleaner and easier to maintain

🕵️ Practical Example: Reading a Configuration File

Consider a scenario where an engineer needs to read a configuration file to load application settings. The file might be missing, corrupted, or temporarily locked. Using try-except-finally ensures the file is always closed properly.

Step-by-step breakdown:

  • try block: Attempt to open the configuration file and read its contents.
  • except FileNotFoundError: Handle the case where the file does not exist by printing a user-friendly message.
  • except PermissionError: Handle the case where the file exists but cannot be accessed due to permissions.
  • except Exception: Catch any other unexpected errors and log them.
  • else block: Executes only if no exception occurred—useful for processing the data successfully.
  • finally block: Check if the file object exists and close it. This runs no matter what happened in the try or except blocks.

Key points to remember: - The else clause runs only when the try block completes without raising an exception. - The finally clause runs regardless of whether an exception was raised or caught. - Always check that the resource variable exists before attempting to close it in the finally block.


🧪 Practical Example: Database Connection Management

Another common use case is managing database connections. A database connection left open can cause serious issues in production environments.

Typical workflow:

  • try block: Establish a connection to the database and execute a query.
  • except ConnectionError: Handle network or authentication failures.
  • except DatabaseError: Handle query-specific errors like invalid SQL syntax.
  • else block: Process the query results if everything succeeded.
  • finally block: Close the database connection to free up server resources.

Why this matters: - Database servers have a limited number of concurrent connections. - Unclosed connections can lead to connection pool exhaustion. - The finally block ensures cleanup even if the query itself fails.


✅ Best Practices for Engineers

  • Always use finally for cleanup when working with files, sockets, or database connections.
  • Consider using the with statement (context managers) for simpler cases—it automatically handles cleanup and is the preferred approach in modern Python.
  • Keep cleanup code minimal in the finally block—only include essential release operations.
  • Avoid returning from finally as it can suppress exceptions or override return values from the try block.
  • Log errors in except blocks but perform cleanup in finally to separate concerns.

📝 Summary

The finally clause is a powerful tool for ensuring that resources are properly released, regardless of whether an exception occurs. By combining try, except, else, and finally, engineers can write code that is both robust and maintainable. This pattern is especially important in production environments where resource leaks can lead to system instability and downtime. Mastering this pattern is a fundamental step toward writing professional-grade Python code.


The finally clause ensures that cleanup code — like closing files or database connections — always runs, whether an error occurred or not.


🔧 Example 1: Basic file closing with finally

This example shows how to guarantee a file is closed after reading, even if an error happens.

file = open("sample.txt", "r")
try:
    content = file.read()
    print(content)
finally:
    file.close()
    print("File closed.")

📤 Output: Contents of sample.txt
File closed.


🔧 Example 2: Handling an error while still closing the file

This example demonstrates that finally runs even when an exception occurs during file operations.

file = open("missing.txt", "r")
try:
    content = file.read()
    print(content)
except FileNotFoundError:
    print("File not found.")
finally:
    file.close()
    print("File closed.")

📤 Output: File not found.
File closed.


🔧 Example 3: Closing a database connection after query

This example shows how to ensure a database connection is closed after a query, regardless of success or failure.

connection = connect_to_database("my_db")
try:
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM users")
    results = cursor.fetchall()
    print(results)
except Exception:
    print("Query failed.")
finally:
    connection.close()
    print("Connection closed.")

📤 Output: [(1, 'Alice'), (2, 'Bob')]
Connection closed.


🔧 Example 4: Using else with finally for clean separation

This example shows how else runs only on success, while finally always runs for cleanup.

file = open("data.txt", "r")
try:
    content = file.read()
except FileNotFoundError:
    print("File not found.")
else:
    print("File read successfully.")
    print(content)
finally:
    file.close()
    print("File closed.")

📤 Output: File read successfully.
Contents of data.txt
File closed.


🔧 Example 5: Practical network socket cleanup

This example demonstrates closing a network socket connection after sending data, with error handling.

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    sock.connect(("example.com", 80))
    sock.send(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
    response = sock.recv(4096)
    print(response[:100])
except ConnectionError:
    print("Connection failed.")
finally:
    sock.close()
    print("Socket closed.")

📤 Output: b'HTTP/1.1 200 OK\r\n...'
Socket closed.


Comparison Table: try-except-else-finally

Clause When it runs Purpose
try Always first Code that might raise an error
except Only if an error occurs Handle specific errors
else Only if no error occurs Code that depends on success
finally Always, after everything else Cleanup (close files, connections)

🔍 Context Introduction

When working with external resources like files, database connections, or network sockets, it is critical to ensure they are properly closed after use—even if an error occurs. Leaving connections or files open can lead to resource leaks, corrupted data, or system instability. Python's finally clause provides a reliable way to execute cleanup code regardless of whether an exception was raised, making it an essential tool for writing robust and production-ready code.


⚙️ Why Closing Resources Matters

  • File handles are limited system resources. Leaving files open can exhaust the available handles and cause other parts of your application to fail.
  • Database connections left open can lock tables, consume memory, and prevent other users from accessing data.
  • Network sockets that are not closed properly can lead to connection timeouts and unpredictable behavior.
  • The finally clause guarantees that your cleanup code runs, whether the code block succeeds or fails with an exception.

🛠️ Basic Pattern: Try-Except-Finally

The standard pattern for safely managing resources follows this flow:

  • try block: Contains the code that might raise an exception (e.g., opening a file or connecting to a database).
  • except block: Handles any exceptions that occur, allowing you to log the error or take corrective action.
  • finally block: Always executes, providing a safe place to close files, release connections, or free other resources.

Example structure: - Open a file for reading inside the try block. - If the file does not exist, the except block catches the FileNotFoundError and prints a meaningful message. - The finally block checks if the file object was created and closes it, preventing a resource leak.


📊 Comparison: With vs. Without Finally

Aspect Without Finally With Finally
Resource cleanup Must be manually repeated in both try and except blocks Written once in the finally block
Risk of forgetting High—easy to miss cleanup in one code path Low—cleanup is guaranteed
Code duplication High—same close logic appears multiple times Low—single cleanup location
Handling unexpected exceptions Cleanup is skipped if an unhandled exception occurs Cleanup always runs, even for unhandled exceptions
Readability More cluttered and error-prone Cleaner and easier to maintain

🕵️ Practical Example: Reading a Configuration File

Consider a scenario where an engineer needs to read a configuration file to load application settings. The file might be missing, corrupted, or temporarily locked. Using try-except-finally ensures the file is always closed properly.

Step-by-step breakdown:

  • try block: Attempt to open the configuration file and read its contents.
  • except FileNotFoundError: Handle the case where the file does not exist by printing a user-friendly message.
  • except PermissionError: Handle the case where the file exists but cannot be accessed due to permissions.
  • except Exception: Catch any other unexpected errors and log them.
  • else block: Executes only if no exception occurred—useful for processing the data successfully.
  • finally block: Check if the file object exists and close it. This runs no matter what happened in the try or except blocks.

Key points to remember: - The else clause runs only when the try block completes without raising an exception. - The finally clause runs regardless of whether an exception was raised or caught. - Always check that the resource variable exists before attempting to close it in the finally block.


🧪 Practical Example: Database Connection Management

Another common use case is managing database connections. A database connection left open can cause serious issues in production environments.

Typical workflow:

  • try block: Establish a connection to the database and execute a query.
  • except ConnectionError: Handle network or authentication failures.
  • except DatabaseError: Handle query-specific errors like invalid SQL syntax.
  • else block: Process the query results if everything succeeded.
  • finally block: Close the database connection to free up server resources.

Why this matters: - Database servers have a limited number of concurrent connections. - Unclosed connections can lead to connection pool exhaustion. - The finally block ensures cleanup even if the query itself fails.


✅ Best Practices for Engineers

  • Always use finally for cleanup when working with files, sockets, or database connections.
  • Consider using the with statement (context managers) for simpler cases—it automatically handles cleanup and is the preferred approach in modern Python.
  • Keep cleanup code minimal in the finally block—only include essential release operations.
  • Avoid returning from finally as it can suppress exceptions or override return values from the try block.
  • Log errors in except blocks but perform cleanup in finally to separate concerns.

📝 Summary

The finally clause is a powerful tool for ensuring that resources are properly released, regardless of whether an exception occurs. By combining try, except, else, and finally, engineers can write code that is both robust and maintainable. This pattern is especially important in production environments where resource leaks can lead to system instability and downtime. Mastering this pattern is a fundamental step toward writing professional-grade Python code.

Interactive Views

You are currently in 📚 All-in-One mode. Use the tabs at the top to switch to 📖 Theory Only or 💻 Code Only views.

The finally clause ensures that cleanup code — like closing files or database connections — always runs, whether an error occurred or not.


🔧 Example 1: Basic file closing with finally

This example shows how to guarantee a file is closed after reading, even if an error happens.

file = open("sample.txt", "r")
try:
    content = file.read()
    print(content)
finally:
    file.close()
    print("File closed.")

📤 Output: Contents of sample.txt
File closed.


🔧 Example 2: Handling an error while still closing the file

This example demonstrates that finally runs even when an exception occurs during file operations.

file = open("missing.txt", "r")
try:
    content = file.read()
    print(content)
except FileNotFoundError:
    print("File not found.")
finally:
    file.close()
    print("File closed.")

📤 Output: File not found.
File closed.


🔧 Example 3: Closing a database connection after query

This example shows how to ensure a database connection is closed after a query, regardless of success or failure.

connection = connect_to_database("my_db")
try:
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM users")
    results = cursor.fetchall()
    print(results)
except Exception:
    print("Query failed.")
finally:
    connection.close()
    print("Connection closed.")

📤 Output: [(1, 'Alice'), (2, 'Bob')]
Connection closed.


🔧 Example 4: Using else with finally for clean separation

This example shows how else runs only on success, while finally always runs for cleanup.

file = open("data.txt", "r")
try:
    content = file.read()
except FileNotFoundError:
    print("File not found.")
else:
    print("File read successfully.")
    print(content)
finally:
    file.close()
    print("File closed.")

📤 Output: File read successfully.
Contents of data.txt
File closed.


🔧 Example 5: Practical network socket cleanup

This example demonstrates closing a network socket connection after sending data, with error handling.

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    sock.connect(("example.com", 80))
    sock.send(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
    response = sock.recv(4096)
    print(response[:100])
except ConnectionError:
    print("Connection failed.")
finally:
    sock.close()
    print("Socket closed.")

📤 Output: b'HTTP/1.1 200 OK\r\n...'
Socket closed.


Comparison Table: try-except-else-finally

Clause When it runs Purpose
try Always first Code that might raise an error
except Only if an error occurs Handle specific errors
else Only if no error occurs Code that depends on success
finally Always, after everything else Cleanup (close files, connections)