PYTHON / ERRORS AND EXCEPTIONS
else and finally
Use else to run code only when the try block succeeded, and finally to run cleanup no matter how the block exits.
What you will learn
- Put success-only code in else so the try block stays narrow
- Know that else is skipped whenever any except handler runs
- Use finally for cleanup that must run on return, raise, or break
- Recognise that return inside finally discards a pending exception
Understanding else and finally
A try statement can have four parts, and each answers a different question. The try block holds the operation that might fail, except handles specific failures, else runs only if the try block finished without raising anything, and finally runs on the way out no matter what happened. The point of else is scope control: anything you put there is outside the protected region, so if it raises, your own except clauses will not catch it and misreport the cause.
That distinction matters more than it first looks. If you write int(text) and then a long stretch of follow-up work inside the same try, an unrelated ValueError from that follow-up work lands in your except ValueError branch and gets reported as a parsing failure. Moving the follow-up work into else makes the try block cover exactly one risky expression, so a handler for that expression can only fire for that expression.
finally is about guaranteed exit work, not about error handling. Python runs it when the block ends normally, when else finishes, when an except handler finishes, when an exception is propagating out uncaught, and even when a return, break, or continue is leaving the block. That last case is where finally gets dangerous: if the finally block itself executes a return, it overrides the pending return value and throws away any exception that was on its way out, silently.
def parse(text):
try:
value = int(text)
except ValueError:
print("not a number:", text)
return None
else:
print("parsed cleanly:", value)
return value
finally:
print("finally for", repr(text))
print("result:", parse("42"))
print("result:", parse("x7"))else runs only on success and is unprotected by the try's handlers, while finally runs on every exit path including return and propagating exceptions.
Worked examples
else is not protected by except
Shows that an exception raised in the else block escapes even when the except clause lists that exception type.
def lookup(d, key):
try:
value = d[key]
except (KeyError, TypeError):
print("handled:", key)
else:
print("length:", len(value))
lookup({"a": "hi"}, "a")
lookup({"a": "hi"}, "b")
try:
lookup({"a": 7}, "a")
except TypeError as e:
print("escaped:", e)Example explained
Line 1d[key] succeeds for "a", so else runs and prints the length of "hi".
Line 2For "b" the subscription raises KeyError, the handler runs, and else is skipped entirely.
Line 3len(7) raises TypeError inside else, and the except clause ignores it despite naming TypeError, because else sits outside the protected block.
Line 4The TypeError therefore propagates to the caller's own try, which prints it.
finally on abnormal exits
Contrasts a finally that correctly runs cleanup while an exception propagates with a finally that returns and swallows the exception.
def swallow():
try:
raise ValueError("boom")
finally:
return "swallowed"
def cleanup():
try:
return 1 / 0
finally:
print("cleanup ran")
print(swallow())
try:
cleanup()
except ZeroDivisionError as e:
print("caught:", e)Example explained
Line 1swallow() raises ValueError, but the return in finally replaces the propagating exception, so the caller sees a normal string result.
Line 2In cleanup(), 1 / 0 raises before the return value exists, yet finally still prints its message.
Line 3Because that finally does not return, the ZeroDivisionError continues outward and is caught by the caller.
Line 4The two functions differ only in whether finally returns, which is the entire difference between losing and keeping the error.
Important notes
else is only legal after at least one except clause; try/else/finally without except is a SyntaxError.
Python 3.14 emits a SyntaxWarning for return, break, or continue that leaves a finally block, precisely because it hides exceptions.
Common mistakes
Returning from finally to "simplify" a function, which silently erases a real exception and turns a crash into wrong data.
Assuming else runs after the except handler, so cleanup or success logging written in else never executes on failure paths.
Leaving all the follow-up work inside try, so a ValueError from that later code is caught by the parsing handler and reported as bad input.
Try it yourself
Change, predict, then run
Write a function that opens a dict lookup in try, prints "ok" in else, and prints "done" in finally, then call it once with a present key and once with a missing key and confirm which lines appear in each call.
Open the Python workspaceCheck your understanding
Why move success-only code out of the try block into an else clause instead of leaving it at the end of try?
- So that exceptions raised by that code are not caught by the try's own except clauses
- Because else runs only after finally has finished, giving cleanup a chance to complete first
- Because statements in else execute faster than statements inside a try block
- Because names bound inside try go out of scope at the end of the block and are only visible in else
Show answer
Code in else is outside the guarded region, so a failure there propagates instead of being misattributed to the risky operation you meant to handle. The finally option is wrong on ordering: else runs before finally, not after it, since finally is always the last thing to execute on the way out.