What's the purpose of the `else` and `finally` clauses in a `try` statement?
Quick Answer
`finally` always runs — on success, on a caught exception, on an uncaught exception, or even on `return`/`break`/`continue` inside the `try` — making it the right place for guaranteed cleanup. `else` runs **only if the `try` block completed with no exception**, and is used to separate "code that might raise" from "code that should only run if it didn't," keeping the `try` block itself minimal and making it clear which lines are actually being guarded.
Detailed Answer
finally: guaranteed cleanup
def read_config(path):
f = open(path)
try:
return f.read()
finally:
f.close() # ALWAYS runs -- success, exception, or even an early return
finally runs no matter how the try block exits: normal completion, a
caught exception, an uncaught exception (it still runs before the
exception propagates further), or control-flow statements like return
inside the try. This unconditional guarantee is why it's the standard
place for releasing resources (though in practice, a context manager —
with open(path) as f: — is usually preferred over manual
try/finally for exactly this pattern).
else: only on success, keeping the try block narrow
try:
value = int(user_input)
except ValueError:
print("not a valid number")
else:
print(f"you entered {value}") # only runs if int() did NOT raise
process(value) # if process() raises, it's NOT caught by except ValueError above!
Without else, a common mistake is putting process(value) inside the
try block itself — which means if process() also happens to raise a
ValueError, it gets misattributed to "not a valid number" even though
the real problem is unrelated to parsing. Moving success-dependent code
into else keeps the try block scoped to exactly the operation you
intend to guard, so unrelated exceptions from subsequent code aren't
accidentally caught by the same handler.
Full combination
def process_file(path):
try:
f = open(path)
except FileNotFoundError:
print("file not found")
return
else:
data = f.read() # only runs if open() succeeded
finally:
try:
f.close() # always attempt cleanup...
except NameError:
pass # ...but f might not exist if open() failed
return data
This shows the intended division of labor: try holds only the
risky operation, except handles its specific failure, else holds
what should happen only on success, and finally holds unconditional
cleanup — each clause has one clear job instead of being crammed
together.
Execution order guarantee
For a successful try: try body → else body → finally body.
For a caught exception: try body (until the exception) → matching
except body → finally body (note: else is skipped whenever an
exception occurred, matched or not).
Interview-ready summary: finally is unconditional cleanup that runs
regardless of outcome; else runs only when the try block succeeded,
letting you separate "the operation that might fail" from "what happens
next only if it didn't" — which avoids accidentally catching unrelated
exceptions raised by follow-up code that has nothing to do with the
original try.