Difficulty: Intermediate
Python's try statement supports two optional clauses beyond except: else and finally. The else clause runs only when the try block completes without raising any exception. The finally clause runs unconditionally, whether an exception occurred or not. Together, these four clauses -- try, except, else, finally -- give you fine-grained control over how your code responds to success, failure, and cleanup.
The else clause is often underused but serves an important purpose. Code in the else block only executes if the try block did not raise an exception. This is better than putting the code at the end of the try block because it makes clear which code you expect might raise exceptions (in try) and which code should only run on success (in else). It also prevents the except clause from accidentally catching exceptions raised by the success-path code.
The finally clause is Python's guarantee for cleanup. No matter what happens -- whether the try block succeeds, an exception is caught, an exception is uncaught, or even if a return statement is encountered -- the finally block will execute. This makes it the ideal place for releasing resources: closing files, disconnecting from databases, releasing locks, or resetting state. In practice, context managers (the with statement) handle most of these cases, but understanding finally is essential.
The full execution order is: try runs first; if an exception occurs, the matching except runs; if no exception occurs, else runs; and finally always runs last regardless. When a return statement appears in try or except, the finally block still runs before the function actually returns. However, if finally itself contains a return statement, it overrides any return from try or except, which is a well-known pitfall.
A common cleanup pattern uses finally to ensure resources are released even when errors occur. Before context managers became widespread, the try/finally pattern was the standard way to guarantee file closure or lock release. You would open a resource before the try block, use it inside try, and close it in finally. This pattern is still valuable when working with resources that do not support the context manager protocol.
def load_config(filename):
try:
with open(filename, 'r') as f:
data = f.read()
except FileNotFoundError:
print(f"Config file '{filename}' not found")
data = None
else:
print(f"Config loaded ({len(data)} chars)")
finally:
print("Config load attempt finished")
return data
result = load_config("nonexistent.cfg")
print(f"Result: {result}")
Since the file does not exist, FileNotFoundError is caught by except. The else block is skipped because an exception occurred. The finally block runs regardless. If the file existed, else would run instead of except.
def divide_and_format(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Step 2: except - division by zero")
return "error"
else:
print(f"Step 2: else - result is {result}")
return f"success: {result}"
finally:
print("Step 3: finally - always runs")
print("Step 1: calling with valid args")
outcome = divide_and_format(10, 3)
print(f"Step 4: outcome = {outcome}")
print("---")
print("Step 1: calling with zero")
outcome = divide_and_format(10, 0)
print(f"Step 4: outcome = {outcome}")
In the success case, else runs then finally runs. In the error case, except runs then finally runs. Notice that finally runs before the return value is actually delivered to the caller.
class Connection:
def __init__(self, name):
self.name = name
self.connected = True
print(f"Connected to {name}")
def query(self, sql):
if "DROP" in sql:
raise PermissionError("DROP not allowed")
return f"Results from {self.name}"
def close(self):
self.connected = False
print(f"Closed connection to {self.name}")
def run_query(sql):
conn = Connection("mydb")
try:
result = conn.query(sql)
except PermissionError as e:
print(f"Query denied: {e}")
else:
print(f"Query result: {result}")
finally:
conn.close()
run_query("SELECT * FROM users")
print("---")
run_query("DROP TABLE users")
The connection is always closed in the finally block, whether the query succeeds or raises a PermissionError. This ensures no resource leaks even when errors occur.
else clause, finally clause, cleanup patterns, resource management, execution order