"Наконец" всегда выполняется в Python?


для любой возможной попытки-наконец блок в 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 88

4 ответа:

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

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

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

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

  • os._exit остановить процесс немедленно без выполнения 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 поднимет a KeyboardInterrupt и пропустить каждую строчку finally содержимое блока. (KeyboardInterrupt-безопасный код очень трудно писать).
  • если компьютер теряет питание, или если он спит и не просыпается,finally блоки не будут работать.

The 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 (это подпадает под "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 является единственным, который будет выполняться.