← Python Code Python's Hidden Traps
Browse Python Concepts

try / except / else / finally — The Full Pattern

Mental Model

Think of try/except/else/finally as a structured pathway: 'try' is the main road. If a detour (exception) occurs, you take the 'except' path. If the main road is clear and you reach the end, you take the 'else' path. Regardless of which path you took, or if you had to leave early (return), you always pass through the 'finally' gate at the very end to exit.

Rule: When using try blocks, put code that must execute only upon success in the 'else' block, and guarantee resource cleanup in the 'finally' block.

The Setup

You are writing a database connection utility. You want to execute a transaction, commit if it succeeds, log failures, and always release the database connection back to the pool. However, you also have a return statement inside the try block, making you unsure of when everything executes.

What Does This Print?

Broken code
Python
def run_transaction():
    try:
        print("1. Starting transaction")
        return "SUCCESS"
    except Exception:
        print("2. Exception caught")
    else:
        print("3. No exceptions occurred")
    finally:
        print("4. Releasing connection")

result = run_transaction()
print(f"Result: {result}")
Predict the exact printed sequence. Does the 'else' block execute if the 'try' block returns early? Does the 'finally' block run after the function returns?

The Output

What actually happens
1. Starting transaction 4. Releasing connection Result: SUCCESS

The else block was completely skipped because the try block exited via an explicit return. However, the finally block ran before the function actually yielded control back to the caller, even though the return statement was executed inside the try block.

Why Python Does This

Python's VM handles finally blocks by utilizing bytecode instructions such as SETUP_FINALLY. If a return, break, or continue statement is encountered within a try block, Python pushes the return value onto the virtual machine stack, but temporarily suspends execution of the return. It then jumps to compile and run the instructions inside the finally block. Only after finally completes is the return value popped off the stack and returned. Note that the else block only runs if the try block completes its execution path normally without hitting exceptions or early returns.

The Fix

Corrected pattern
Python
def run_transaction():
    try:
        print("1. Starting transaction")
        status = "SUCCESS"
    except Exception as e:
        print(f"2. Exception caught: {e}")
        status = "FAILURE"
    else:
        # Runs ONLY if try completed successfully without early returns
        print("3. Committing transaction")
    finally:
        # Guarantees resource cleanup regardless of execution path
        print("4. Releasing connection")
    return status

The 'finally' block is guaranteed to execute regardless of how the 'try' block is exited (normally, via an exception, or via an explicit 'return' or 'break'). This ensures critical cleanup code runs, providing strong guarantees for resource management. The 'else' block specifically runs only if the 'try' block completes without an exception, making it ideal for code that should only execute upon successful completion.

How This Fails in Real Systems

A telemetry service parsed log files, returned early on completion, but relied on a manual connection close in a later portion of the function instead of a finally block. When a parsing exception occurred, the file handles and sockets remained open, causing the Linux server to run out of file descriptors after 12 hours.

Key Takeaway

When using try blocks, put code that must execute only upon success in the 'else' block, and guarantee resource cleanup in the 'finally' block.
Common mistake: Developers misinterpret the 'else' block's execution, sometimes assuming it runs if no exception occurred at all, or failing to realize 'finally' runs even if 'return' is called in the 'try'.