“ finally”总是在 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 中执行失败的情况?

48371 次浏览

是也不是。

可以保证的是 Python 将始终尝试执行 finally 块。在从块返回或引发未捕获异常的情况下,就在实际返回或引发异常之前执行 finally 块。

(您可以通过简单地运行问题中的代码来控制自己)

我可以想象的惟一不执行 finally 块的情况是当 Python 解释器本身崩溃(例如在 C 代码中)或者因为断电而崩溃。

是的,最后总是赢。

唯一的方法是在 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!')
...
$

我现在正在运行这个代码,来测试是否在宇宙热死之后,finally 仍然会执行:

try:
while True:
sleep(1)
finally:
print('done')

不过,我还在等结果,所以稍后再来看看吧。

“保证”这个词比任何 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 试图通过抛入一个 GeneratorExit异常来运行 finally块,但是在这里我们捕捉到这个异常,然后又是 yield,这时 Python 会打印一个警告(“生成器忽略了 GeneratorExit”)并放弃。详情请参阅 PEP342(通过增强型发生器的协同程序)

    生成器或协同程序可能无法执行到结论的其他方式包括对象从未被 GC‘ ed (是的,这是可能的,即使在 CPython 中) ,或者 __aexit__中的 async with await,或者 finally块中的对象 awaityield。此列表并非详尽无遗。

  • 如果首先退出所有非守护进程线程,则守护进程线程中的 finally可能永远不会执行

  • 在不执行 finally块的情况下,os._exit将立即 停止进程。

  • os.fork可能导致 finally阻断到 执行两次。如果对共享资源的访问不是 正确同步,那么除了会出现两次事件所带来的普通问题之外,还可能导致并发访问冲突(崩溃、停滞、 ...)。

    由于 multiprocessing在使用 < em > fork start 方法时使用 fork-without-exec 创建辅助进程(Unix 上的默认设置) ,然后在辅助进程完成工作后调用 os._exit,因此 finallymultiprocessing的交互可能会出现问题(例子)。

  • C 级内存区段错误将阻止 finally块运行。
  • kill -SIGKILL将阻止 finally块运行。除非您自己安装一个处理程序来控制关机,否则 SIGTERMSIGHUP也将阻止 finally块的运行; 默认情况下,Python 不处理 SIGTERMSIGHUP
  • finally中的异常可能会阻止清理完成。一个特别值得注意的情况是,当我们开始执行 finally块时,如果用户点击 control-C只是。Python 将生成一个 KeyboardInterrupt并跳过 finally块的每一行内容。(KeyboardInterrupt安全的代码很难写)。
  • 如果计算机断电,或者它处于休眠状态而没有醒来,finally块就不会运行。

finally块不是事务系统; 它不提供原子性保证或任何类似的东西。这些例子中的一些可能看起来是显而易见的,但是很容易忘记这样的事情可能会发生,并且过多地依赖于 finally

根据 Python 文档:

不管先前发生了什么,一旦代码块完成并且处理了所有引发的异常,就会执行 final-block。即使在异常处理程序或 else-block 中出现错误并引发新的异常,final-block 中的代码仍在运行。

还应该注意的是,如果有多个 return 语句,包括 finally 块中的一个,那么 finally 块 return 是唯一将执行的返回语句。

我在没有使用生成器函数的情况下找到了这个:

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 块。

另外,如果在 try 块中的 sleep ()调用之后添加 bar = bazz行。然后第一个到达这一行的进程抛出一个异常(因为没有定义 bazz) ,这会导致它自己的 finally 块运行,但是随后会杀死 map,导致其他 try 块消失而没有到达它们的 finally 块,第一个进程也没有到达它的 return 语句。

这对于 Python 多处理来说意味着,如果某个进程可能有异常,那么您就不能信任异常处理机制来清理所有进程中的资源。需要额外的信号处理或管理多处理映射调用之外的资源。

您可以将 finally 与 if 语句一起使用,下面的例子是检查网络连接,如果它已连接,它将运行 finally 块

            try:


reader1, writer1 = loop.run_until_complete(self.init_socket(loop))


x = 'connected'


except:


print("cant connect server transfer") #open popup


x = 'failed'


finally  :
                

if x == 'connected':


with open('text_file1.txt', "r") as f:


file_lines = eval(str(f.read()))


else:
print("not connected")