Does 'finally' always execute in Python?
对于python中任何可能的try finally块,是否保证始终执行
例如,假设我在
1 2 3 4 5 6 | try: 1/0 except ZeroDivisionError: return finally: print("Does this code run?") |
或者我再养一只
1 2 3 4 5 6 | try: 1/0 except ZeroDivisionError: raise finally: print("What about this code?") |
号
测试表明,
在任何情况下,
"保证"这个词比任何
如果对象从未执行到结论,那么生成器或异步协程中的
finally 可能永远不会运行。有很多种可能发生的方法;这里有一种:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def 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试图通过抛出一个
GeneratorExit 异常来运行finally 块,但是在这里我们捕获了这个异常,然后又一次执行yield ,这时python会打印一个警告("generator忽略generatorexit")并放弃。有关详细信息,请参阅PEP 342(通过增强的生成器进行协同工作)。生成器或协同程序可能无法执行以得出结论的其他方式包括:如果对象只是从未被提取过(是的,这是可能的,即使在cpython中),或者如果对象
async with await 在__aexit__ 中,或者如果对象await s或yield 在finally 块中。这份清单并非详尽无遗。如果所有非守护进程线程都先退出,则守护进程线程中的
finally 可能永远不会执行。os._exit 将立即停止进程,而不执行finally 块。os.fork 可能导致finally 块执行两次。如果对共享资源的访问没有正确同步,这可能会导致并发访问冲突(崩溃、暂停等),这与您预期发生两次的正常问题一样。由于
multiprocessing 在使用fork start方法(Unix上的默认方法)时使用不带exec的fork来创建工作进程,然后在工作进程中调用os._exit ,一旦工作进程完成,finally 和multiprocessing 交互可能会有问题(例如)。- C级分段故障将阻止
finally 块运行。 kill -SIGKILL 将阻止finally 块运行。SIGTERM 和SIGHUP 也会阻止finally 块运行,除非您自己安装一个处理程序来控制关闭;默认情况下,python不处理SIGTERM 或SIGHUP 。小精灵finally 中的异常可以阻止清理完成。一个特别值得注意的情况是,当我们开始执行finally 块时,用户点击control-c。python将提升一个KeyboardInterrupt 并跳过finally 块内容的每一行。(KeyboardInterrupt 安全代码很难编写)。- 如果计算机断电,或者休眠而不唤醒,
finally 块将无法运行。
对。最终总是胜利。
唯一的方法是在
I imagine there are other scenarios I haven't thought of.
号
以下是一些你可能没有想到的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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' |
根据退出解释器的方式,有时您最终可以"取消",但不能这样:
1 2 3 4 5 6 7 8 | >>> import sys >>> try: ... sys.exit() ... finally: ... print('finally wins!') ... finally wins! $ |
号
使用不稳定的
1 2 3 4 5 6 7 | >>> import os >>> try: ... os._exit(1) ... finally: ... print('finally!') ... $ |
我目前正在运行此代码,以测试宇宙热死后是否最终仍将执行:
1 2 3 4 5 | try: while True: sleep(1) finally: print('done') |
。
不过,我仍在等待结果,请稍后再来查看。
根据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.
号
还应该注意的是,如果有多个返回语句,包括finally块中的一个,那么finally块返回是唯一将要执行的语句。
嗯,是和否。
可以保证的是,python将始终尝试执行finally块。在从块返回或引发未捕获异常的情况下,最终块将在实际返回或引发异常之前执行。
(只需运行问题中的代码就可以控制自己的行为)
我能想象的唯一不执行finally块的情况是当python解释器本身崩溃时,例如在C代码内部或者由于断电。
我发现这个没有使用生成器函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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),这会导致异常。该异常会在不允许其他进程到达其最终块的情况下终止映射。
另外,如果在try块中的sleep()调用之后添加行
对于Python多处理来说,这意味着,如果任何一个进程都有异常,就不能信任异常处理机制来清理所有进程中的资源。需要额外的信号处理或管理多处理映射调用之外的资源。
要真正了解它的工作原理,只需运行以下两个示例:
-
1
2
3
4
5
6try:
1
except:
print 'except'
finally:
print 'finally'号
将输出
finally
号
-
1
2
3
4
5
6try:
1/0
except:
print 'except'
finally:
print 'finally'将输出
except
finally号