对于 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
除了抛出异常什么都不做。真是用词不当。
“保证”这个词比任何 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
。
是的。最后总是赢。
打败它的唯一方法是在 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
in a generator or coroutine can quite easily fail to execute,而不会接近“解释器崩溃”的情况。
sleep(1)
肯定会导致不确定的行为。 :-D
os._exit
就所有实际目的而言,与引发崩溃(不干净的退出)相同。正确的退出方式是sys.exit
。
无论之前发生了什么,一旦代码块完成并处理任何引发的异常,就会执行最终块。即使异常处理程序或 else 块中存在错误并且引发了新异常,最终块中的代码仍会运行。
还需要注意的是,如果有多个 return 语句,包括 finally 块中的一个,那么 finally 块 return 是唯一会执行的。
嗯,是的,也不是。
可以保证的是 Python 将始终尝试执行 finally 块。在您从块返回或引发未捕获的异常的情况下,finally 块在实际返回或引发异常之前执行。
(您可以通过简单地运行问题中的代码来控制自己)
我能想象的唯一不会执行 finally 块的情况是 Python 解释器本身崩溃,例如在 C 代码中或由于断电。
我发现这个没有使用生成器函数:
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 多处理意味着,即使其中一个进程可能有异常,您也不能信任异常处理机制来清理所有进程中的资源。额外的信号处理或管理多处理映射调用之外的资源是必要的。
您可以将 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")
不定期副业成功案例分享
except
,并且永远不要在生成器中捕获GeneratorExit
。关于线程/杀死进程/段错误/关机的要点是预期的,python 不能做魔术。另外:finally
中的异常显然是个问题,但这并不会改变控制流已 移至finally
块的事实。关于Ctrl+C
,您可以添加一个忽略它的信号处理程序,或者在当前操作完成后简单地“安排”一次干净的关闭。kill -9
的观点没有指定语言。坦率地说,它需要重复,因为它处于盲点。太多的人忘记了,或者没有意识到,他们的程序可能会在不被允许清理的情况下被完全阻止。finally
块,就好像他们提供了交易保证一样。似乎很明显,他们没有,但这并不是每个人都意识到的。至于生成器的情况,生成器有很多方法可能根本不会被 GC,并且生成器或协程可能会在GeneratorExit
之后意外产生很多方法,即使它没有捕获GeneratorExit
,例如,如果async with
挂起__exit__
中的协程。