How does `try/except/else/finally` work in Python, and what is the execution order?
Python's exception handling has four clauses:
try: The code that might raise an exceptionexcept: Handles specific exceptions if they occurelse: Runs ONLY if no exception was raised intry-- useful for code that should run only on successfinally: ALWAYS runs, whether an exception occurred or not -- used for cleanup
Execution order:
- No exception:
try->else->finally - Exception caught:
try(partial) ->except->finally - Exception not caught:
try(partial) ->finally-> exception propagates
The else clause is often overlooked but is important for separating the "protected" code (in try) from the "success path" code (in else). This prevents accidentally catching exceptions from the success code.
The finally clause runs even if there's a return, break, or continue in try or except.
try:
# Code that might raise
except SpecificError as e:
# Handle the error
else:
# Only runs if NO exception in try
finally:
# ALWAYS runs (cleanup)
Execution paths:
Success: try -> else -> finally
Caught: try -> except -> finally
Uncaught: try -> finally -> propagate
# Basic try/except/else/finally
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Cannot divide by zero")
return None
except TypeError as e:
print(f"Type error: {e}")
return None
else:
# Only runs if no exception in try
print(f"{a} / {b} = {result}")
return result
finally:
# Always runs
print("Division operation complete")
print(divide(10, 3))
# 10 / 3 = 3.3333...
# Division operation complete
# 3.333...
print(divide(10, 0))
# Cannot divide by zero
# Division operation complete
# None
# Why else matters -- separating protected code from success code
def read_config(path):
try:
f = open(path)
except FileNotFoundError:
print(f"Config not found: {path}")
return {}
else:
# This code is NOT protected by except
# If json.loads raises, it propagates (which is correct!)
import json
data = json.loads(f.read())
f.close()
return data
# finally runs even with return
def example():
try:
return "from try"
finally:
print("finally runs!")
result = example()
# finally runs!
print(result) # "from try"
# CAUTION: return in finally overrides return in try
def dangerous():
try:
return "try value"
finally:
return "finally value" # overrides try's return!
print(dangerous()) # "finally value" -- surprising!
# Multiple except clauses
def process(data):
try:
value = int(data)
result = 100 / value
except ValueError:
return "Not a number"
except ZeroDivisionError:
return "Cannot be zero"
except (TypeError, AttributeError) as e:
return f"Bad input: {e}"
else:
return result
- •else runs only if no exception; finally always runs
- •Use else to separate protected code from success-path code
- •finally runs even with return, break, or continue
- •Multiple except clauses handle different exception types
- •return in finally overrides return in try or except
- •Bare except (except:) catches BaseException including SystemExit and KeyboardInterrupt
- •else prevents accidentally catching exceptions from success code
- •finally runs even if except re-raises the exception