For any possible try-finally block in Python, is it guaranteed that the finally
block will always be executed?
For example, let’s say I return while in an except
block:
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
Or maybe I re-raise an Exception
:
try:
1/0
except ZeroDivisionError:
raise
finally:
print("What about this code?")
Testing shows that finally
does get executed for the above examples, but I imagine there are other scenarios I haven't thought of.
Are there any scenarios in which a finally
block can fail to execute in Python?
"Guaranteed" is a much stronger word than any implementation of
finally
deserves. What is guaranteed is that if execution flows out of the wholetry
-finally
construct, it will pass through thefinally
to do so. What is not guaranteed is that execution will flow out of thetry
-finally
.A
finally
in a generator or async coroutine might never run, if the object never executes to conclusion. There are a lot of ways that could happen; here's one:Note that this example is a bit tricky: when the generator is garbage collected, Python attempts to run the
finally
block by throwing in aGeneratorExit
exception, but here we catch that exception and thenyield
again, at which point Python prints a warning ("generator ignored GeneratorExit") and gives up. See PEP 342 (Coroutines via Enhanced Generators) for details.Other ways a generator or coroutine might not execute to conclusion include if the object is just never GC'ed (yes, that's possible, even in CPython), or if an
async with
await
s in__aexit__
, or if the objectawait
s oryield
s in afinally
block. This list is not intended to be exhaustive.A
finally
in a daemon thread might never execute if all non-daemon threads exit first.os._exit
will halt the process immediately without executingfinally
blocks.os.fork
may causefinally
blocks to execute twice. As well as just the normal problems you'd expect from things happening twice, this could cause concurrent access conflicts (crashes, stalls, ...) if access to shared resources is not correctly synchronized.Since
multiprocessing
uses fork-without-exec to create worker processes when using the fork start method (the default on Unix), and then callsos._exit
in the worker once the worker's job is done,finally
andmultiprocessing
interaction can be problematic (example).finally
blocks from running.kill -SIGKILL
will preventfinally
blocks from running.SIGTERM
andSIGHUP
will also preventfinally
blocks from running unless you install a handler to control the shutdown yourself; by default, Python does not handleSIGTERM
orSIGHUP
.finally
can prevent cleanup from completing. One particularly noteworthy case is if the user hits control-C just as we're starting to execute thefinally
block. Python will raise aKeyboardInterrupt
and skip every line of thefinally
block's contents. (KeyboardInterrupt
-safe code is very hard to write).finally
blocks won't run.The
finally
block is not a transaction system; it doesn't provide atomicity guarantees or anything of the sort. Some of these examples might seem obvious, but it's easy to forget such things can happen and rely onfinally
for too much.Well, yes and no.
What is guaranteed is that Python will always try to execute the finally block. In the case where you return from the block or raise an uncaught exception, the finally block is executed just before actually returning or raising the exception.
(what you could have controlled yourself by simply running the code in your question)
The only case I can imagine where the finally block will not be executed is when the Python interpretor itself crashes for example inside C code or because of power outage.
According to the Python documentation:
It should also be noted that if there are multiple return statements, including one in the finally block, then the finally block return is the only one that will execute.
I found this one without using a generator function:
The sleep can be any code that might run for inconsistent amounts of time.
What appears to be happening here is that the first parallel process to finish leaves the try block successfully, but then attempts to return from the function a value (foo) that hasn't been defined anywhere, which causes an exception. That exception kills the map without allowing the other processes to reach their finally blocks.
Also, if you add the line
bar = bazz
just after the sleep() call in the try block. Then the first process to reach that line throws an exception (because bazz isn't defined), which causes its own finally block to be run, but then kills the map, causing the other try blocks to disappear without reaching their finally blocks, and the first process not to reach its return statement, either.What this means for Python multiprocessing is that you can't trust the exception-handling mechanism to clean up resources in all processes if even one of the processes can have an exception. Additional signal handling or managing the resources outside the multiprocessing map call would be necessary.
Yes. Finally always wins.
The only way to defeat it is to halt execution before
finally:
gets a chance to execute (e.g. crash the interpreter, turn off your computer, suspend a generator forever).Here are a couple more you may not have thought about:
Depending on how you quit the interpreter, sometimes you can "cancel" finally, but not like this:
Using the precarious
os._exit
(this falls under "crash the interpreter" in my opinion):I'm currently running this code, to test if finally will still execute after the heat death of the universe:
However, I'm still waiting on the result, so check back here later.
To really understand how it works, just run these two examples:
will output
will output