backend / python core / tricky questions / 20_exception_swallowing_in_finally.md

Exception swallowing in finally

1 min read source

Exception swallowing in finally

The gotcha

A return, break, or continue inside finally silently suppresses the active exception. The exception you thought would propagate is gone.

Minimal repro

def f():
    try:
        raise ValueError("boom")
    finally:
        return 42      # swallows the ValueError

print(f())   # 42 — no exception raised

Same with break/continue in a loop:

for i in range(3):
    try:
        raise ValueError("boom")
    finally:
        break          # swallows the exception, exits the loop
print("survived")

Why it happens

Python’s spec: if finally clause executes a return/break/continue, that flow control supersedes any pending exception or in-progress return. The exception is just dropped.

This is rarely intentional. It’s almost always a bug — someone wrote finally: return last_value to “always return something” and accidentally swallowed errors.

How to avoid

  • Don’t put return in finally. Set state in the try and return after the block, or wrap differently.
  • If you genuinely want to convert exceptions to return values, do it explicitly with except:
def f():
    try:
        return compute()
    except ValueError:
        return 42      # explicit fallback
    # finally only does cleanup, no return
  • Use contextlib.suppress for “ignore this error” patterns, which is at least visible:
from contextlib import suppress
with suppress(FileNotFoundError):
    os.remove("maybe.txt")
  • Linters: pylint flags return-in-finally (W0150) and flake8-bugbear has B012.

Bonus: exceptions raised in finally

If the body raises and finally raises a different exception, the original is “chained” via __context__ but the new one propagates. Python 3 keeps the original visible in the traceback.

try:
    raise ValueError("first")
finally:
    raise RuntimeError("second")
# traceback shows: "during handling of the above ValueError, the following exception occurred"

Interview angle

“What does this print and what does it raise?” with a try/raise/finally/return snippet. The unwary answer “it raises ValueError” is wrong — the return in finally swallowed it.