Para qualquer bloco try-finally possível no Python, é garantido que o finally
bloco será sempre executado?
Por exemplo, digamos que eu retorne enquanto estiver em um except
bloco:
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
Ou talvez eu re-levante um Exception
:
try:
1/0
except ZeroDivisionError:
raise
finally:
print("What about this code?")
O teste mostra que finally
é executado para os exemplos acima, mas imagino que há outros cenários em que não pensei.
Existem cenários nos quais um finally
bloco pode falhar na execução em Python?
python
exception-handling
try-catch-finally
finally
Stevoisiak
fonte
fonte
finally
falha ao executar ou "anular seu objetivo" é durante um loop infinitosys.exit
ou uma interrupção forçada. A documentação afirma quefinally
sempre é executada, então eu iria com isso.finally
não será executado. Ou o mesmo se o computador travar antes: Dfinally
não será executado se o cabo de alimentação for arrancado da parede.Respostas:
"Garantido" é uma palavra muito mais forte do que qualquer implementação de
finally
merece. O que é garantido é que, se a execução fluir para fora do todotry
-finally
construa, ela passará pelo processofinally
. O que não é garantido é que a execução sairá dotry
-finally
.A
finally
em um gerador ou em uma rotina assíncrona pode nunca ser executada , se o objeto nunca for executado até a conclusão. Existem muitas maneiras de isso acontecer; aqui está um:Observe que este exemplo é um pouco complicado: quando o gerador é coletado de lixo, o Python tenta executar o
finally
bloco lançando umaGeneratorExit
exceção, mas aqui capturamos essa exceção e,yield
novamente, quando o Python imprime um aviso ("gerador ignorado GeneratorExit ") e desiste. Consulte PEP 342 (Coroutines através de geradores aprimorados) para obter detalhes.Outras maneiras um gerador ou co-rotina pode não executar para conclusão incluem se o objeto nunca é GC'ed (sim, isso é possível, mesmo em CPython), ou se um
async with
await
s em__aexit__
, ou se o objetoawait
s ouyield
s em umfinally
bloco. Esta lista não pretende ser exaustiva.A
finally
em um encadeamento daemon pode nunca ser executado se todos os encadeamentos que não sejam daemon sairem primeiro.os._exit
interromperá o processo imediatamente sem executarfinally
blocos.os.fork
pode causarfinally
a execução de blocos duas vezes . Além dos problemas normais que você esperaria que acontecesse duas vezes, isso poderia causar conflitos de acesso simultâneos (travamentos, paralisações, ...) se o acesso a recursos compartilhados não estiver sincronizado corretamente .Como
multiprocessing
usa fork-sem-exec para criar processos de trabalho ao usar o método de início de forquilha (o padrão no Unix), e depois chamaos._exit
o trabalhador assim que o trabalho do trabalhador é concluído,finally
e amultiprocessing
interação pode ser problemática ( exemplo ).finally
execução de blocos.kill -SIGKILL
impedirá afinally
execução de blocos.SIGTERM
eSIGHUP
também impedirá afinally
execução de blocos, a menos que você instale um manipulador para controlar o desligamento; Por padrão, o Python não manipulaSIGTERM
ouSIGHUP
.finally
pode impedir que a limpeza seja concluída. Um caso particularmente digno de nota é se o usuário pressionar o controle-C, assim que começarmos a executar ofinally
bloco. Python irá gerarKeyboardInterrupt
e pular todas as linhasfinally
do conteúdo do bloco. (oKeyboardInterrupt
código -safe é muito difícil de escrever).finally
blocos não serão executados.O
finally
bloco não é um sistema de transações; não fornece garantias de atomicidade nem nada do tipo. Alguns desses exemplos podem parecer óbvios, mas é fácil esquecer que essas coisas podem acontecer e confiarfinally
demais.fonte
except
e nunca pegueGeneratorExit
dentro de um gerador. Os pontos sobre threads / eliminação do processo / segfaulting / power off são esperados, o python não pode fazer mágica. Além disso: as exceções emfinally
são obviamente um problema, mas isso não altera o fato de que o fluxo de controle foi movido para ofinally
bloco. Em relação aCtrl+C
, você pode adicionar um manipulador de sinal que o ignore ou simplesmente "agendar" um desligamento limpo após a conclusão da operação atual.kill -9
não especificou um idioma. E, francamente, ele precisa ser repetido, porque fica em um ponto cego. Muitas pessoas esquecem, ou não percebem, que seu programa poderia ser interrompido sem sucesso, mesmo sem permissão para limpar.finally
blocos como se tivessem fornecido garantias transacionais. Pode parecer óbvio que não, mas não é algo que todos percebem. Quanto ao gabinete do gerador, existem várias maneiras de um gerador não ter o GC ativado, e muitas maneiras que um gerador ou uma rotina pode acidentalmente ceder após,GeneratorExit
mesmo que não capte oGeneratorExit
, por exemplo, se umasync with
suspender uma corotina em__exit__
.Sim. Finalmente sempre vence.
A única maneira de derrotá-lo é interromper a execução antes de
finally:
ter a chance de executar (por exemplo, travar o intérprete, desligar o computador, suspender um gerador para sempre).Aqui estão mais algumas que você pode não ter pensado:
Dependendo de como você saiu do intérprete, às vezes você pode "cancelar" finalmente, mas não assim:
Usando o precário
os._exit
(isso se enquadra em "travar o intérprete" na minha opinião):Atualmente, estou executando este código, para testar se finalmente ainda será executado após a morte por calor do universo:
No entanto, ainda estou esperando o resultado, então volte aqui mais tarde.
fonte
finally
em um gerador ou corotina pode facilmente falhar na execução , sem chegar nem perto de uma condição "travar o intérprete".sleep(1)
definitivamente resultaria em um comportamento indefinido. :-Dos._exit
é, para todos os efeitos práticos, o mesmo que induzir um acidente (saída impura). A maneira correta de sair ésys.exit
.De acordo com a documentação do Python :
Também deve ser observado que, se houver várias instruções de retorno, incluindo uma no bloco final, o retorno do bloco final será o único que será executado.
fonte
Bem, sim e não.
O que é garantido é que o Python sempre tentará executar o bloco final. No caso em que você retorna do bloco ou gera uma exceção não capturada, o bloco final é executado imediatamente antes de realmente retornar ou gerar a exceção.
(o que você poderia ter se controlado simplesmente executando o código em sua pergunta)
O único caso em que posso imaginar onde o bloco final não será executado é quando o próprio interpretador Python trava, por exemplo, no código C ou por falta de energia.
fonte
Eu encontrei este sem usar uma função de gerador:
O sono pode ser qualquer código que possa ser executado por quantidades inconsistentes de tempo.
O que parece estar acontecendo aqui é que o primeiro processo paralelo a ser concluído deixa o bloco try com êxito, mas tenta retornar da função um valor (foo) que não foi definido em nenhum lugar, o que causa uma exceção. Essa exceção mata o mapa sem permitir que os outros processos alcancem seus blocos finalmente.
Além disso, se você adicionar a linha
bar = bazz
logo após a chamada sleep () no bloco try. Em seguida, o primeiro processo a alcançar essa linha lança uma exceção (porque bazz não está definido), o que faz com que seu próprio bloco finalmente seja executado, mas mata o mapa, fazendo com que os outros blocos de tentativa desapareçam sem alcançar seus blocos finalmente, e o primeiro processo a não alcançar sua declaração de retorno também.O que isso significa para o multiprocessamento Python é que você não pode confiar no mecanismo de tratamento de exceções para limpar recursos em todos os processos, se mesmo um dos processos puder ter uma exceção. Seria necessário manipular sinais adicionais ou gerenciar recursos fora da chamada do mapa de multiprocessamento.
fonte
Adendo à resposta aceita, apenas para ajudar a ver como funciona, com alguns exemplos:
Este:
irá produzir
irá produzir
fonte