Всегда ли "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
await
s в__aexit__
, или если объектawait
илиyield
s в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 является единственным, который будет выполняться.