'finally' 是否总是在 Python 中执行?

Does 'finally' always execute in Python?

对于 Python 中任何可能的 try-finally 块,是否保证 finally 块将始终执行?

例如,假设我 return 在 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 确实执行了上述示例,但我想还有其他我没有想到的场景。

是否存在 Python 中 finally 块无法执行的情况?

好吧,是也不是。

保证的是Python会一直尝试执行finally块。如果您从块 return 或引发未捕获的异常,finally 块将在实际 return 或引发异常之前执行。

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

我能想到的唯一不会执行 finally 块的情况是当 Python 解释器本身崩溃时,例如在 C 代码中或因为断电。

是的。 最后总是赢。

打败它的唯一方法是在 finally: 有机会执行之前停止执行(例如,使解释器崩溃、关闭计算机、永远挂起生成器)。

I imagine there are other scenarios I haven't thought of.

这里还有一些你可能没有想到的:

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'

根据您退出解释器的方式,有时您最终可以 "cancel",但不是这样的:

>>> import sys
>>> try:
...     sys.exit()
... finally:
...     print('finally wins!')
... 
finally wins!
$

使用不稳定的os._exit(我认为这属于"crash the interpreter"):

>>> import os
>>> try:
...     os._exit(1)
... finally:
...     print('finally!')
... 
$

我目前运行这段代码,测试finally在宇宙热寂后是否仍会执行:

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

但是,我还在等待结果,所以稍后再回来查看。

"Guaranteed" 是一个比 finally 的任何实现都应得的更强大的词。可以保证的是,如果执行流出整个 try-finally 构造,它将通过 finally 来执行。不能保证的是执行会流出try-finally.

  • A finally in a generator or async coroutine might never run,如果对象永远不会执行到结论。有很多可能发生的方式;这是一个:

    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 打印警告 ("generator ignored GeneratorExit") 并放弃。有关详细信息,请参阅 PEP 342 (Coroutines via Enhanced Generators)

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

  • A finally in a daemon thread might never execute 如果所有 non-daemon 线程先退出。

  • os._exit will halt the process immediately 不执行 finally 个块。

  • os.fork 可能导致 finally 块到 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.

    因为 multiprocessing 在使用 fork start method (the default on Unix), and then calls os._exit in the worker once the worker's job is done, finally and multiprocessing interaction can be problematic (example 时使用 fork-without-exec 创建工作进程)。

  • 一个 C-level 分段错误将阻止 finally 块从 运行 宁。
  • kill -SIGKILL 将阻止 finally 块来自 运行ning。 SIGTERMSIGHUP 也将阻止 finally 块从 运行ning 除非你安装一个处理程序来控制自己关闭;默认情况下,Python 不处理 SIGTERMSIGHUP.
  • finally 中的异常可能会阻止清理完成。一个特别值得注意的情况是,如果用户在我们开始执行 finally 块时点击 control-C just。 Python 将引发 KeyboardInterrupt 并跳过 finally 块内容的每一行。 (KeyboardInterrupt-安全代码很难写)。
  • 如果计算机断电,或者如果它休眠并且没有唤醒,finally 块将不会 运行。

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

根据 Python documentation:

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.

还需要注意的是,如果有多个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 块,但随后尝试从函数中 return 一个尚未在任何地方定义的值 (foo),这会导致异常。该异常会在不允许其他进程到达它们的 finally 块的情况下终止映射。

此外,如果您在 try 块中的 sleep() 调用之后添加行 bar = bazz。然后到达该行的第一个进程抛出异常(因为 bazz 未定义),这导致它自己的 finally 块成为 运行,但随后杀死映射,导致其他 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")