ChatGPT解决这个技术问题 Extra ChatGPT

“终于”总是在 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 中无法执行的情况?

我能想象的唯一情况 finally 无法执行或“失败”是在无限循环、sys.exit 或强制中断期间。 documentation 声明 finally 总是被执行,所以我会这样做。
如果电源线从墙上撕下,finally 将不会执行。
您可能对有关 C# 的同一问题的回答感兴趣:stackoverflow.com/a/10260233/88656
在一个空的信号量上阻止它。永远不要发出信号。完毕。
@Xay sys.exit 除了抛出异常什么都不做。真是用词不当。

u
user2357112

“保证”这个词比任何 finally 的实现都强得多。可以保证的是,如果执行流出整个 try-finally 构造,它将通过 finally 执行此操作。不能保证执行将流出 try-finally

如果对象永远不会执行到结论,那么生成器或异步协程中的 finally 可能永远不会运行。有很多可能发生的方式;这里有一个: def gen(text): try: for line in text: try: yield int(line) except: # 忽略空行 - 但捕获太多!最后通过: 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”)并放弃。有关详细信息,请参阅 PEP 342(通过增强生成器的协程)。生成器或协程可能不会执行到结论的其他方式包括对象是否从未被 GC 处理(是的,这是可能的,即使在 CPython 中也是如此),或者如果在 __aexit__ 中使用 await 的异步,或者如果对象在 a最后阻塞。此列表并非详尽无遗。

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

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

os.fork 可能会导致 finally 块执行两次。除了您对发生两次的事情所期望的正常问题外,如果对共享资源的访问未正确同步,这可能会导致并发访问冲突(崩溃、停顿等)。由于 multiprocessing 在使用 fork start 方法(Unix 上的默认设置)时使用 fork-without-exec 创建 worker 进程,然后一旦 worker 的工作完成后在 worker 中调用 os._exit,finally 和 multiprocessing 交互可能会出现问题(例如)。

C 级分段错误将阻止 finally 块运行。

kill -SIGKILL 将阻止 finally 块运行。 SIGTERM 和 SIGHUP 也将阻止 finally 块运行,除非您自己安装处理程序来控制关闭;默认情况下,Python 不处理 SIGTERM 或 SIGHUP。

finally 中的异常可能会阻止清理完成。一个特别值得注意的情况是,如果用户在我们开始执行 finally 块时按下了 control-C。 Python 将引发 KeyboardInterrupt 并跳过 finally 块内容的每一行。 (键盘中断安全代码很难编写)。

如果计算机断电,或者如果它休眠并且没有唤醒,finally 块将不会运行。

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


我相信只有您列表中的第一点是真正相关的,并且有一个简单的方法可以避免它:1)永远不要使用裸 except,并且永远不要在生成器中捕获 GeneratorExit。关于线程/杀死进程/段错误/关机的要点是预期的,python 不能做魔术。另外:finally 中的异常显然是个问题,但这并不会改变控制流 移至 finally 块的事实。关于 Ctrl+C,您可以添加一个忽略它的信号处理程序,或者在当前操作完成后简单地“安排”一次干净的关闭。
提到 kill -9 在技术上是正确的,但有点不公平。任何语言编写的程序都不会在收到 kill -9 时运行任何代码。事实上,根本没有任何程序会收到 kill -9,所以即使它想要,它也无法执行任何操作。这就是 kill -9 的全部意义所在。
@Tom:关于 kill -9 的观点没有指定语言。坦率地说,它需要重复,因为它处于盲点。太多的人忘记了,或者没有意识到,他们的程序可能会在不被允许清理的情况下被完全阻止。
@GiacomoAlzetta:有些人依赖 finally 块,就好像他们提供了交易保证一样。似乎很明显,他们没有,但这并不是每个人都意识到的。至于生成器的情况,生成器有很多方法可能根本不会被 GC,并且生成器或协程可能会在 GeneratorExit 之后意外产生很多方法,即使它没有捕获 GeneratorExit ,例如,如果 async with 挂起 __exit__ 中的协程。
@user2357112 是的-几十年来我一直在努力让开发人员在应用程序启动时清理临时文件等,而不是退出。靠所谓的‘收拾收拾’,是在求失望和泪水:)
w
wim

是的。最后总是赢。

打败它的唯一方法是在 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')

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


或在 try catch 中有一个 i 有限循环
finally in a generator or coroutine can quite easily fail to execute,而不会接近“解释器崩溃”的情况。
在宇宙时间的热寂不复存在后,sleep(1) 肯定会导致不确定的行为。 :-D
@StevenVascellaro 我认为这没有必要 - os._exit 就所有实际目的而言,与引发崩溃(不干净的退出)相同。正确的退出方式是sys.exit
@wim 您对 While 循环有任何更新吗? :P 谢谢你的回答,这些例子对我有帮助
S
Stevoisiak

根据Python documentation

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

还需要注意的是,如果有多个 return 语句,包括 finally 块中的一个,那么 finally 块 return 是唯一会执行的。


S
Serge Ballesta

嗯,是的,也不是。

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

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

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


哈哈..或者try catch中有一个无限循环
我认为“嗯,是的,不是的”是最正确的。 finally: always wins 其中“always”意味着解释器能够运行并且“finally:”的代码仍然可用,“wins”被定义为解释器将尝试运行 finally: 块并且它会成功。那是“是”,这是非常有条件的。 “否”是解释器在“最终”之前可能停止的所有方式:- 电源故障,硬件故障,针对解释器的 kill -9,解释器或它所依赖的代码中的错误,其他挂起解释器的方式。以及挂在“finally:”里面的方法。
B
Blair Houghton

我发现这个没有使用生成器函数:

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 块运行,但随后会终止映射,导致其他 try 块在没有到达它们的 finally 块的情况下消失,并且第一个进程也没有达到它的返回语句。

这对于 Python 多处理意味着,即使其中一个进程可能有异常,您也不能信任异常处理机制来清理所有进程中的资源。额外的信号处理或管理多处理映射调用之外的资源是必要的。


F
Foton

您可以将 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")