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

Ste*_*ica 116 python exception-handling finally try-catch-finally

对于Python中任何可能的try-finally块,是否可以保证finally块总是被执行?

例如,假设我在一个except块中返回:

try:
    1/0
except ZeroDivisionError:
    return
finally:
    print("Does this code run?")
Run Code Online (Sandbox Code Playgroud)

或许我重新提出一个Exception:

try:
    1/0
except ZeroDivisionError:
    raise
finally:
    print("What about this code?")
Run Code Online (Sandbox Code Playgroud)

测试显示finally上面的例子确实执行了,但我想还有其他我没有想过的场景.

是否有任何情况下finally块无法在Python中执行?

use*_*ica 182

"保证"比任何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.')
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,此示例有点棘手:当生成器被垃圾收集时,Python会尝试finally通过抛出GeneratorExit异常来运行块,但是在这里我们捕获该异常然后yield再次,此时Python打印出警告("生成器忽略了GeneratorExit ")并放弃.有关详细信息,请参阅PEP 342(通过增强型发生器协同程序).

    生成器或协同程序可能无法执行结束的其他方式包括对象是否只是从来没有GC(是的,这是可能的,即使在CPython中),或者如果是async with awaitin __aexit__,或者如果对象awaits或yields在finally块中.此列表并非详尽无遗.

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

  • os._exit将立即停止进程而不执行finally块.

  • os.fork可能导致finally执行两次.除了您期望发生两次事情的正常问题之外,如果未正确同步对共享资源的访问,这可能会导致并发访问冲突(崩溃,停顿......).

    因为multiprocessing在使用fork start方法(Unix上的默认方法)时使用fork-without-exec来创建工作进程,然后os._exit在工作人员的工作完成后调用worker,finally并且multiprocessing交互可能会有问题(例如).

  • C级分段错误将阻止finally块运行.
  • kill -SIGKILL将阻止finally块运行.SIGTERM并且SIGHUP还会阻止finally块运行,除非你自己安装一个处理程序来控制关闭; 默认情况下,Python不处理SIGTERMSIGHUP.
  • 一个例外finally可以防止清理完成.其中特别值得注意的情况是,如果用户点击控制-C 只是因为我们已经开始执行该finally块.Python将引发KeyboardInterrupt并跳过finally块内容的每一行.(KeyboardInterrupt-safe代码很难写).
  • 如果计算机断电,或者计算机休眠并且没有唤醒,则finally块将无法运行.

finally块不是交易系统; 它不提供原子性保证或任何类型的保证.其中一些例子可能看起来很明显,但很容易忘记这些事情可能发生并依赖finally太多.

  • 我相信只有列表的第一点才真正相关,并且有一种简单的方法可以避免它:1)永远不要使用裸``except`,并且永远不要在生成器中捕获`GeneratorExit`.有关线程/杀死进程/ segfaulting/power off的观点是预期的,python无法做到魔术.另外:`finally`中的异常显然是一个问题,但这并没有改变控制流*被移动到`finally`块的事实.关于`Ctrl + C`,您可以添加一个忽略它的信号处理程序,或者只是在当前操作完成后"调度"一个干净的关闭. (13认同)
  • @Tom:关于`kill -9`的观点没有指定语言.坦率地说,它需要重复,因为它处于盲点.太多人忘记或者没有意识到,他们的程序可能会被阻止在其轨道上,甚至不被允许清理. (10认同)
  • 提到*kill -9*在技术上是正确的,但有点不公平.任何语言编写的程序在收到kill -9时都不会运行任何代码.事实上,没有任何程序可以**接受**杀戮-9,所以即使它想要,它也无法执行任何操作.这就是杀人-9的全部要点. (8认同)
  • @GiacomoAlzetta:有些人依赖于`finally`块,好像他们提供了交易保证.看起来很明显他们没有,但并不是每个人都意识到的.至于生成器的情况,生成器可能有很多种方法可能根本没有GC,并且很多方式发生器或协同程序可能会在"GeneratorExit"之后意外地产生,即使它没有捕获`GeneratorExit`例如,如果`async with`挂起`__exit__`中的协程. (4认同)
  • @ user2357112是的 - 我几十年来一直试图让开发人员在app启动时清理临时文件等,而不是退出.依靠所谓的"清理和优雅终止",是要求失望和泪水:) (2认同)

wim*_*wim 67

是. 最后总是赢.

击败它的唯一方法是在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'
Run Code Online (Sandbox Code Playgroud)

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

>>> import sys
>>> try:
...     sys.exit()
... finally:
...     print('finally wins!')
... 
finally wins!
$
Run Code Online (Sandbox Code Playgroud)

使用不稳定os._exit(在我看来,这属于"崩解翻译"):

>>> import os
>>> try:
...     os._exit(1)
... finally:
...     print('finally!')
... 
$
Run Code Online (Sandbox Code Playgroud)

我目前正在运行此代码,以测试在宇宙热死之后是否仍将执行:

try:
    while True:
       sleep(1)
finally:
    print('done')
Run Code Online (Sandbox Code Playgroud)

但是,我还在等待结果,请稍后再回来查看.

  • 在宇宙热死亡时间不复存在之后,所以`sleep(1)`肯定会导致不确定的行为.:-D (22认同)
  • [`finally`在生成器或协同程序中很容易无法执行](https://ideone.com/eQZzna),不会出现"崩溃解释器"状态. (8认同)
  • 或者在try catch中有一个有限循环 (5认同)
  • @StevenVascellaro我不认为这是必要的 - 对于所有实际目的,`os._exit`与引发崩溃(不洁退出)相同.退出的正确方法是`sys.exit`. (2认同)
  • @wim 你有关于 While 循环的任何更新吗?:P 感谢您的回答,这些例子对我很有帮助 (2认同)

小智 9

根据Python文档:

无论先前发生了什么,一旦代码块完成并且处理了任何引发的异常,就执行最终块.即使异常处理程序或else-block中存在错误并引发新异常,最终块中的代码仍会运行.

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


Ser*_*sta 8

嗯,是的,不.

保证是Python总是会尝试执行finally块.如果从块返回或引发未捕获的异常,则在实际返回或引发异常之前执行finally块.

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

我可以想象唯一一个不会执行finally块的情况是Python解释器本身崩溃,例如在C代码内或因断电而崩溃.

  • 它没有被捕获或没有被捕获? (2认同)