try / except / else / finally — The Full Pattern
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.
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?
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}")
The Output
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
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.