Всегда ли "finally" выполняется в Python?

для любого возможного блока try-finally в Python гарантируется, что 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?

4 ответов


"гарантировано" - гораздо более сильное слово, чем любая реализация finally заслуживает. Гарантируется, что если выполнение вытекает из целого try -finally построить, он пройдет через finally для этого. Что не гарантируется, так это то, что выполнение будет вытекать из try -finally.

  • A finally в генераторе или async сопрограмма может не работать, если объект никогда не выполняется до завершения. Есть много способов это может случиться; вот один:

    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 (Coroutines через усиленные генераторы) для сведения.

    другие пути генератор или coroutine может не выполняться для вывода include, если объект просто никогда не GC'Ed (да, это возможно, даже в CPython), или если async with awaits в __aexit__, или если объект awaitили yields в finally блок. Этот перечень не является исчерпывающим.

  • A finally в потоке демона может никогда не выполняться если все не-демонические потоки выходят первыми.

  • os._exit остановить процесс немедленно!--48--> без выполнения finally блоки.

  • os.fork может привести к finally блоки выполнить два раза. Как и обычные проблемы, которые вы ожидаете от того, что происходит дважды, это может вызвать одновременные конфликты доступа (сбои, киоски, ...) если доступ к общим ресурсам не правильно синхронизированы.

    С multiprocessing использует fork-without-exec для создания рабочих процессов, когда с помощью вилки метод start (по умолчанию в Unix), а затем называет os._exit в рабочем, как только работа рабочего выполнена,finally и multiprocessing взаимодействие может быть проблематично (пример).

  • ошибка сегментации уровня C предотвратит finally блокирует запуск.
  • kill -SIGKILL помешает finally блокирует запуск. SIGTERM и SIGHUP также поможет избежать finally блоки от запуска, если вы установите обработчик для управления выключением самостоятельно; по умолчанию Python не обрабатывает SIGTERM или SIGHUP.
  • исключение finally может предотвратить завершение очистки. Один особенно примечательный случай, если пользователь нажимает control-C просто как мы начинаем исполнять finally блок. Python поднимет KeyboardInterrupt и пропустить каждую строчку finally содержимое блока. (KeyboardInterrupt-безопасный код очень трудно писать).
  • если компьютер теряет сила, или если она спит и не просыпается,finally блоки не будут работать.

на finally block не является транзакционной системой; он не предоставляет гарантий атомарности или чего-либо подобного. Некоторые из этих примеров могут показаться очевидными, но легко забыть, что такие вещи могут произойти и положиться на 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 (это подпадает под "crash The interpreter"на мой взгляд):

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

в настоящее время я запускаю этот код, чтобы проверить, будет ли он выполняться после тепловой смерти Вселенной:

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

тем не менее, я все еще жду результата, поэтому проверьте здесь позже.


по словам документация Python:

независимо от того, что произошло ранее, окончательный блок выполняется после завершения блока кода и обработки любых поднятых исключений. Даже если в обработчике исключений или блоке else возникает ошибка и возникает новое исключение, код в последнем блоке все равно выполняется.

следует также отметить, что если существует несколько операторов return, включая один в блоке finally, тогда возврат блока finally является единственным, который будет выполняться.