Для любого возможного блока try-finally в Python всегда ли будет выполняться блок finally?

Например, допустим, я возвращаюсь, находясь в блоке , кроме:

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 может не выполняться в Python?

Ответы (5)

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

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

    def gen (текст):
        пытаться:
            для строки в тексте:
                пытаться:
                    yield int (строка)
                Кроме:
                    # Игнорируйте пустые строки - но ловите слишком много!
                    проходят
        наконец-то:
            print ('Выполняется важная очистка')
    
    текст = ['1', '', '2', '', '3']
    
    если есть (n> 1 для n в gen (текст)):
        print ('Нашел номер')
    
    print ('Ой, без очистки.')
    

    Обратите внимание, что этот пример немного сложен: когда генератор собирает мусор, Python пытается запустить блок finally, выбрасывая исключение GeneratorExit, но здесь мы улавливаем это исключение, а затем снова yield, после чего Python выводит предупреждение («генератор проигнорировал GeneratorExit») и сдается. Подробнее см. PEP 342 (сопрограммы через расширенные генераторы).

    Другие способы, которыми генератор или сопрограмма могут не выполнить до завершения, включают, если объект просто никогда не GC'ed (да, это возможно, даже в CPython), или если async с awaits в __ aexit __, или если объект awaits или yields в блоке finally. Этот список не является исчерпывающим.

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

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

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

    Так как multiprocessing использует fork-without-exec для создания рабочих процессов при использовании fork метода запуска (по умолчанию в 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 не является системой транзакций; он не предоставляет гарантий атомарности или чего-либо в этом роде. Некоторые из этих примеров могут показаться очевидными, но легко забыть, что такие вещи могут случаться, и слишком сильно полагаться на , наконец,.

Я нашел это без использования функции генератора:

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.

Также, если вы добавите строку bar = bazz сразу после вызова sleep () в блоке try. Затем первый процесс, достигший этой строки, выдает исключение (поскольку bazz не определен), которое вызывает запуск собственного блока finally, но затем уничтожает карту, в результате чего другие блоки try исчезают, не достигнув своих блоков finally, и первый процесс также не достиг своего оператора возврата.

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

Согласно документации Python:

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

Следует также отметить, что при наличии нескольких операторов return, включая один в блоке 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!')
... 
$

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

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

Однако я все еще жду результата, так что загляните сюда позже.

Ну и да, и нет.

Гарантируется, что Python всегда будет пытаться выполнить блок finally. В случае, когда вы возвращаетесь из блока или вызываете неперехваченное исключение, блок finally выполняется непосредственно перед фактическим возвратом или вызовом исключения.

(что бы вы могли контролировать сами, просто запустив код из вашего вопроса)

Единственный случай, который я могу представить, когда блок finally не будет выполнен, - это когда сам интерпретатор Python выйдет из строя, например, внутри кода C или из-за отключения электроэнергии.

2022 WebDevInsider