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