“Guaranteed” is a much stronger word than any implementation of finally deserves. What is guaranteed is that if execution flows out of the whole try–finally construct, it will pass through the finally to do so. What is not guaranteed is that execution will flow out of the try–finally.
-
A
finallyin 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:def gen(text): try: for line in text: try: yield int(line) except: # Ignore blank lines - but catch too much! pass finally: print('Doing important cleanup') text = ['1', '', '2', '', '3'] if any(n > 1 for n in gen(text)): print('Found a number') print('Oops, no cleanup.')Note that this example is a bit tricky: when the generator is garbage collected, Python attempts to run the
finallyblock by throwing in aGeneratorExitexception, but here we catch that exception and thenyieldagain, 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 withawaits in__aexit__, or if the objectawaits oryields in afinallyblock. This list is not intended to be exhaustive. -
A
finallyin a daemon thread might never execute if all non-daemon threads exit first. -
os._exitwill halt the process immediately without executingfinallyblocks. -
os.forkmay causefinallyblocks 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
multiprocessinguses fork-without-exec to create worker processes when using the fork start method (the default on Unix), and then callsos._exitin the worker once the worker’s job is done,finallyandmultiprocessinginteraction can be problematic (example). - A C-level segmentation fault will prevent
finallyblocks from running. kill -SIGKILLwill preventfinallyblocks from running.SIGTERMandSIGHUPwill also preventfinallyblocks from running unless you install a handler to control the shutdown yourself; by default, Python does not handleSIGTERMorSIGHUP.- An exception in
finallycan prevent cleanup from completing. One particularly noteworthy case is if the user hits control-C just as we’re starting to execute thefinallyblock. Python will raise aKeyboardInterruptand skip every line of thefinallyblock’s contents. (KeyboardInterrupt-safe code is very hard to write). - If the computer loses power, or if it hibernates and doesn’t wake up,
finallyblocks 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 on finally for too much.