"Наконец" всегда выполняется в 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 ответа:
"гарантированный" - это гораздо более сильное слово, чем любая реализация
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 with
await
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 поднимет aKeyboardInterrupt
и пропустить каждую строчку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 является единственным, который будет выполняться.