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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/49262379/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 19:02:16  来源:igfitidea点击:

Does 'finally' always execute in Python?

pythonexception-handlingtry-catch-finallyfinally

提问by Stevoisiak

For any possible try-finally block in Python, is it guaranteed that the finallyblock will always be executed?

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

For example, let's say I return while in an exceptblock:

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

try:
    1/0
except ZeroDivisionError:
    return
finally:
    print("Does this code run?")

Or maybe I re-raise an Exception:

或者我重新加注Exception

try:
    1/0
except ZeroDivisionError:
    raise
finally:
    print("What about this code?")

Testing shows that finallydoes get executed for the above examples, but I imagine there are other scenarios I haven't thought of.

测试表明finally上面的例子确实得到了执行,但我想还有其他我没有想到的场景。

Are there any scenarios in which a finallyblock can fail to execute in Python?

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

回答by user2357112 supports Monica

"Guaranteed" is a much stronger word than any implementation of finallydeserves. What is guaranteed is that if execution flows out of the whole try-finallyconstruct, it will pass through the finallyto do so. What is not guaranteed is that execution will flow out of the try-finally.

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

  • A finallyin a generator or async coroutine might never run, if the object never executes to conclusion. There are a lot of ways that could happen; here's one:

    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.')
    

    Note that this example is a bit tricky: when the generator is garbage collected, Python attempts to run the finallyblock by throwing in a GeneratorExitexception, but here we catch that exception and then yieldagain, at which point Python prints a warning ("generator ignored GeneratorExit") and gives up. See PEP 342 (Coroutines via Enhanced Generators)for details.

    Other ways a generator or coroutine might not execute to conclusion include if the object is just never GC'ed (yes, that's possible, even in CPython), or if an async withawaits in __aexit__, or if the object awaits or yields in a finallyblock. This list is not intended to be exhaustive.

  • A finallyin a daemon thread might never executeif all non-daemon threads exit first.

  • os._exitwill halt the process immediatelywithout executing finallyblocks.

  • os.forkmay cause finallyblocks to 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.

    Since multiprocessinguses fork-without-exec to create worker processes when using the forkstart method(the default on Unix), and then calls os._exitin the worker once the worker's job is done, finallyand multiprocessinginteraction can be problematic (example).

  • A C-level segmentation fault will prevent finallyblocks from running.
  • kill -SIGKILLwill prevent finallyblocks from running. SIGTERMand SIGHUPwill also prevent finallyblocks from running unless you install a handler to control the shutdown yourself; by default, Python does not handle SIGTERMor SIGHUP.
  • An exception in finallycan prevent cleanup from completing. One particularly noteworthy case is if the user hits control-C justas we're starting to execute the finallyblock. Python will raise a KeyboardInterruptand skip every line of the finallyblock's contents. (KeyboardInterrupt-safe code is very hard to write).
  • If the computer loses power, or if it hibernates and doesn't wake up, finallyblocks won't run.
  • 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 尝试finally通过抛出GeneratorExit异常来运行块,但在这里我们捕获该异常,然后yield再次捕获该异常,此时 Python 打印一个警告(“生成器忽略了 GeneratorExit ") 并放弃。有关详细信息,请参阅PEP 342(通过增强生成器的协程)

    生成器或协程可能不会执行得出结论的其他方式包括对象是否从未被 GC(是的,这是可能的,即使在 CPython 中),或者async withawaits in __aexit__,或者对象awaits 或yields 在finally块中。此列表并非详尽无遗。

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

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

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

    由于multiprocessing在使用forkstart 方法(Unix 上的默认值)时使用 fork-without-exec 创建工作进程,然后os._exit在工作人员的工作完成后调用工作人员,finally并且multiprocessing交互可能会出现问题(示例)。

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

The finallyblock is not a transaction system; it doesn't provide atomicity guarantees or anything of the sort. Some of these examples might seem obvious, but it's easy to forget such things can happen and rely on finallyfor too much.

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

回答by wim

Yes. Finally always wins.

是的。 最后总是赢。

The only way to defeat it is to halt execution before finally:gets a chance to execute (e.g. crash the interpreter, turn off your computer, suspend a generator forever).

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

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

我想还有其他我没有想到的场景。

Here are a couple more you may not have thought about:

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

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'

Depending on how you quit the interpreter, sometimes you can "cancel" finally, but not like this:

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

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

Using the precarious os._exit(this falls under "crash the interpreter" in my opinion):

使用不稳定的os._exit(在我看来,这属于“使解释器崩溃”):

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

I'm currently running this code, to test if finally will still execute after the heat death of the universe:

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

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

However, I'm still waiting on the result, so check back here later.

但是,我仍在等待结果,因此请稍后再回来查看。

回答by jayce

According to the Python documentation:

根据Python 文档

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.

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

It should also be noted that if there are multiple return statements, including one in the finally block, then the finally block return is the only one that will execute.

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

回答by Serge Ballesta

Well, yes and no.

嗯,是和不是。

What is guaranteed is that Python will always try to execute the finally block. In the case where you return from the block or raise an uncaught exception, the finally block is executed just before actually returning or raising the exception.

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

(what you could have controlled yourself by simply running the code in your question)

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

The only case I can imagine where the finally block will not be executed is when the Python interpretor itself crashes for example inside C code or because of power outage.

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

回答by Blair Houghton

I found this one without using a generator function:

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

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)

The sleep can be any code that might run for inconsistent amounts of time.

睡眠可以是任何可能运行时间不一致的代码。

What appears to be happening here is that the first parallel process to finish leaves the try block successfully, but then attempts to return from the function a value (foo) that hasn't been defined anywhere, which causes an exception. That exception kills the map without allowing the other processes to reach their finally blocks.

这里似乎发生的是,第一个完成的并行进程成功离开了 try 块,但随后尝试从函数返回一个尚未在任何地方定义的值 (foo),这会导致异常。该异常会杀死映射,而不允许其他进程到达它们的 finally 块。

Also, if you add the line bar = bazzjust after the sleep() call in the try block. Then the first process to reach that line throws an exception (because bazz isn't defined), which causes its own finally block to be run, but then kills the map, causing the other try blocks to disappear without reaching their finally blocks, and the first process not to reach its return statement, either.

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

What this means for Python multiprocessing is that you can't trust the exception-handling mechanism to clean up resources in all processes if even one of the processes can have an exception. Additional signal handling or managing the resources outside the multiprocessing map call would be necessary.

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

回答by Basj

To really understand how it works, just run these two examples:

要真正了解它是如何工作的,只需运行以下两个示例:

  • try:
        1
    except:
        print 'except'
    finally:
        print 'finally'
    

    will output

    finally

  • try:
        1/0
    except:
        print 'except'
    finally:
        print 'finally'
    

    will output

    except
    finally

  • try:
        1
    except:
        print 'except'
    finally:
        print 'finally'
    

    会输出

    最后

  • try:
        1/0
    except:
        print 'except'
    finally:
        print 'finally'
    

    会输出

    除了
    最后