'finally' 总是在 Python 中执行吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/49262379/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does 'finally' always execute in Python?
提问by Stevoisiak
For any possible try-finally block in Python, is it guaranteed that the finally
block will always be executed?
对于 Python 中任何可能的 try-finally 块,是否保证该finally
块将始终被执行?
For example, let's say I return while in an except
block:
例如,假设我在一个except
块中返回:
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
Or maybe I re-raise an Exception
:
或者我重新加注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.
测试表明finally
上面的例子确实得到了执行,但我想还有其他我没有想到的场景。
Are there any scenarios in which a finally
block can fail to execute in Python?
finally
在 Python 中是否存在块无法执行的情况?
回答by user2357112 supports Monica
"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
.
“保证”是一个比任何finally
应得的实现都要强得多的词。可以保证的是,如果执行流出整个try
-finally
构造,它将通过finally
这样做。不能保证的是执行将流出try
- 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: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
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 executeif all non-daemon threads exit first.os._exit
will halt the process immediatelywithout 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 forkstart 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).- A C-level segmentation fault will prevent
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
.- An exception in
finally
can prevent cleanup from completing. One particularly noteworthy case is if the user hits control-C justas 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). - If the computer loses power, or if it hibernates and doesn't wake up,
finally
blocks won't run.
一
finally
中一台发电机或异步协同程序可能永远不会运行,如果对象根本不会执行到结束。可能发生的方式有很多;这是一个: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.')
请注意,这个示例有点棘手:当生成器被垃圾收集时,Python 尝试
finally
通过抛出GeneratorExit
异常来运行块,但在这里我们捕获该异常,然后yield
再次捕获该异常,此时 Python 打印一个警告(“生成器忽略了 GeneratorExit ") 并放弃。有关详细信息,请参阅PEP 342(通过增强生成器的协程)。生成器或协程可能不会执行得出结论的其他方式包括对象是否从未被 GC(是的,这是可能的,即使在 CPython 中),或者
async with
await
s in__aexit__
,或者对象await
s 或yield
s 在finally
块中。此列表并非详尽无遗。os._exit
将立即停止进程而不执行finally
块。os.fork
可能会导致finally
块执行两次。除了您期望的事情发生两次的正常问题之外,如果对共享资源的访问没有正确同步,这可能会导致并发访问冲突(崩溃、停顿……)。由于
multiprocessing
在使用forkstart 方法(Unix 上的默认值)时使用 fork-without-exec 创建工作进程,然后os._exit
在工作人员的工作完成后调用工作人员,finally
并且multiprocessing
交互可能会出现问题(示例)。- C 级分段错误将阻止
finally
块运行。 kill -SIGKILL
将阻止finally
块运行。SIGTERM
并且SIGHUP
还将阻止finally
块运行,除非您安装处理程序来自己控制关闭;默认情况下,Python 不处理SIGTERM
或SIGHUP
。- 中的异常
finally
可能会阻止清理完成。其中特别值得注意的情况是,如果用户点击控制-C只是因为我们已经开始执行该finally
块。Python 将引发 aKeyboardInterrupt
并跳过finally
块内容的每一行。(KeyboardInterrupt
-safe 代码很难编写)。 - 如果计算机断电,或者它休眠并且没有唤醒,
finally
块将不会运行。
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.
的finally
块不是交易系统; 它不提供原子性保证或任何类似的东西。其中一些示例可能看起来很明显,但很容易忘记这些事情可能会发生并finally
过度依赖。
回答by wim
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).
打败它的唯一方法是在finally:
有机会执行之前停止执行(例如,使解释器崩溃,关闭计算机,永远挂起生成器)。
I imagine there are other scenarios I haven't thought of.
我想还有其他我没有想到的场景。
Here are a couple more you may not have thought about:
这里还有一些你可能没有想过的:
def foo():
# finally always wins
try:
return 1
finally:
return 2
def bar():
# even if he has to eat an unhandled exception, finally wins
try:
raise Exception('boom')
finally:
return 'no boom'
Depending on how you quit the interpreter, sometimes you can "cancel" finally, but not like this:
根据您退出解释器的方式,有时您最终可以“取消”,但不是这样:
>>> import sys
>>> try:
... sys.exit()
... finally:
... print('finally wins!')
...
finally wins!
$
Using the precarious os._exit
(this falls under "crash the interpreter" in my opinion):
使用不稳定的os._exit
(在我看来,这属于“使解释器崩溃”):
>>> import os
>>> try:
... os._exit(1)
... finally:
... print('finally!')
...
$
I'm currently running this code, to test if finally will still execute after the heat death of the universe:
我目前正在运行此代码,以测试在宇宙热死后最终是否仍会执行:
try:
while True:
sleep(1)
finally:
print('done')
However, I'm still waiting on the result, so check back here later.
但是,我仍在等待结果,因此请稍后再回来查看。
回答by jayce
According to the Python documentation:
根据Python 文档:
No matter what happened previously, the final-block is executed once the code block is complete and any raised exceptions handled. Even if there's an error in an exception handler or the else-block and a new exception is raised, the code in the final-block is still run.
无论之前发生了什么,一旦代码块完成并处理了任何引发的异常,就会执行最终块。即使异常处理程序或 else 块中出现错误并引发新异常,最终块中的代码仍会运行。
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.
还需要注意的是,如果有多个return语句,包括finally块中的一个,那么finally块的return是唯一会执行的。
回答by Serge Ballesta
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.
可以保证的是,Python 将始终尝试执行 finally 块。如果您从块返回或引发未捕获的异常,则 finally 块将在实际返回或引发异常之前执行。
(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.
我能想象到的唯一情况是当 Python 解释器本身崩溃时,例如在 C 代码中或由于断电而不会执行 finally 块。
回答by Blair Houghton
I found this one without using a generator function:
我在不使用生成器函数的情况下找到了这个:
import multiprocessing
import time
def fun(arg):
try:
print("tried " + str(arg))
time.sleep(arg)
finally:
print("finally cleaned up " + str(arg))
return foo
list = [1, 2, 3]
multiprocessing.Pool().map(fun, list)
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.
这里似乎发生的是,第一个完成的并行进程成功离开了 try 块,但随后尝试从函数返回一个尚未在任何地方定义的值 (foo),这会导致异常。该异常会杀死映射,而不允许其他进程到达它们的 finally 块。
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.
此外,如果您bar = bazz
在 try 块中的 sleep() 调用之后添加该行。然后到达该行的第一个进程抛出异常(因为未定义 bazz),这会导致其自己的 finally 块运行,但随后杀死了地图,导致其他 try 块消失而没有到达它们的 finally 块,并且第一个进程也没有到达它的 return 语句。
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.
这对于 Python 多处理意味着您不能信任异常处理机制来清理所有进程中的资源,即使其中一个进程可能有异常。需要额外的信号处理或管理多处理映射调用之外的资源。
回答by Basj
To really understand how it works, just run these two examples:
要真正了解它是如何工作的,只需运行以下两个示例:
try: 1 except: print 'except' finally: print 'finally'
will output
finally
try: 1/0 except: print 'except' finally: print 'finally'
will output
except
finally
try: 1 except: print 'except' finally: print 'finally'
会输出
最后
try: 1/0 except: print 'except' finally: print 'finally'
会输出
除了
最后