问题:“最终”是否总是在Python中执行?
对于Python中任何可能的try-finally块,是否保证finally
将始终执行该块?
例如,假设我在一个except
街区中返回:
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
或者,也许我重新提出一个Exception
:
try:
1/0
except ZeroDivisionError:
raise
finally:
print("What about this code?")
测试表明finally
上述示例确实可以执行,但我想我还没有想到其他场景。
在任何情况下,某个finally
块可能无法在Python中执行?
回答 0
“保证”一词比任何finally
应得的实现都要强大得多。什么是保证的是,如果执行全部的流出try
– finally
结构,它会通过finally
这样做。无法保证执行将流出try
– finally
。
一
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’ed(是的,这是可能的,即使在CPython的),或者如果
async with
await
S IN__aexit__
,或者如果对象await
S或yield
S IN一个finally
块。此列表并非详尽无遗。os.fork
可能导致finally
块执行两次。如果您对共享资源的访问未正确同步,则可能会导致并发访问冲突(崩溃,停顿等),这不仅会发生两次常见的正常问题,还会导致并发访问冲突。由于
multiprocessing
在使用fork start方法(Unix上的默认设置)时使用fork-without-exec创建工作进程,然后os._exit
在工作程序完成后调用工作程序,finally
因此multiprocessing
交互可能会出现问题(示例)。- C级分段故障将阻止
finally
块运行。 kill -SIGKILL
将阻止finally
块运行。SIGTERM
并且SIGHUP
也将阻止finally
运行,除非你安装一个处理器来控制自己的关断块; 默认情况下,Python不处理SIGTERM
或SIGHUP
。- 中的异常
finally
会阻止清理完成。其中特别值得注意的情况是,如果用户点击控制-C 只是因为我们已经开始执行该finally
块。Python将引发KeyboardInterrupt
并跳过该finally
块内容的每一行。(KeyboardInterrupt
-safe代码很难编写)。 - 如果计算机断电,或者休眠且无法唤醒,
finally
则无法运行数据块。
该finally
区块不是交易系统;它不提供原子性保证或任何种类的保证。这些示例中的一些可能看起来很明显,但是很容易忘记这种事情可能发生并且finally
过于依赖。
回答 1
是。 最后总是胜利。
克服它的唯一方法是在finally:
有机会执行之前停止执行(例如,使解释器崩溃,关闭计算机,永远暂停生成器)。
我想还有其他我没想到的情况。
您可能还没有想到以下几点:
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'
根据您退出解释器的方式,有时您可以最终“取消”,但不是这样:
>>> import sys
>>> try:
... sys.exit()
... finally:
... print('finally wins!')
...
finally wins!
$
使用不稳定的方法os._exit
(在我看来,这属于“使解释器崩溃”的原因):
>>> import os
>>> try:
... os._exit(1)
... finally:
... print('finally!')
...
$
我当前正在运行以下代码,以测试在宇宙热死之后,是否最终仍然可以执行:
try:
while True:
sleep(1)
finally:
print('done')
但是,我仍在等待结果,因此请稍后再检查。
回答 2
根据Python文档:
无论以前发生了什么,一旦代码块完成并处理了所有引发的异常,便会执行final块。即使异常处理程序或else块中存在错误,并且引发了新的异常,final块中的代码仍将运行。
还应注意,如果有多个return语句,包括finally块中的一个语句,则finally块返回是唯一将执行的语句。
回答 3
好吧,是的,不是。
可以保证的是,Python将始终尝试执行finally块。如果您从该块返回或引发未捕获的异常,则在实际返回或引发异常之前执行finally块。
(只要运行问题中的代码,您本可以控制自己的一切)
我能想象的唯一情况是在Python解释器本身崩溃(例如在C代码内部或由于断电)时,将不会执行finally块。
回答 4
我没有使用生成器功能就发现了这一点:
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)
睡眠可以是可能运行时间不一致的任何代码。
这里出现的情况是,第一个完成的并行处理成功地离开了try块,但随后尝试从该函数返回一个在任何地方都未定义的值(foo),这会导致异常。该异常会杀死映射,而不允许其他进程到达其finally块。
另外,如果您bar = bazz
在try块中的sleep()调用之后添加该行。然后,到达该行的第一个进程将引发异常(因为未定义bazz),这将导致运行其自己的finally块,但随后杀死该映射,从而导致其他try块消失而未到达其finally块,并且第一个过程也不到达其return语句。
这对于Python多处理意味着什么,即使哪一个进程都可能有异常,您也不能相信异常处理机制来清理所有进程中的资源。在多处理映射调用之外需要其他信号处理或管理资源。
回答 5
接受的答案的附录,只是为了帮助了解它的工作原理,并提供了一些示例:
这个:
try: 1 except: print 'except' finally: print 'finally'
将输出
最后
try: 1/0 except: print 'except' finally: print 'finally'
将输出
除了
最后