"Finalmente" viene sempre eseguito in Python?


126

Per ogni possibile blocco di prova finale in Python, è garantito che il finallyblocco verrà sempre eseguito?

Ad esempio, diciamo che torno mentre sono in un exceptblocco:

try:
    1/0
except ZeroDivisionError:
    return
finally:
    print("Does this code run?")

O forse rilancio di nuovo un Exception:

try:
    1/0
except ZeroDivisionError:
    raise
finally:
    print("What about this code?")

I test mostrano che finallyviene eseguito per gli esempi precedenti, ma immagino che ci siano altri scenari a cui non ho pensato.

Esistono scenari in cui un finallyblocco non può essere eseguito in Python?


18
L'unico caso che posso immaginare di finallynon riuscire a eseguire o "sconfiggere il suo scopo" è durante un ciclo infinito sys.exito un'interruzione forzata. La documentazione afferma che finallyviene sempre eseguito, quindi lo seguirò.
Xay

1
Un po 'di pensiero laterale e sicuramente non quello che hai chiesto, ma sono abbastanza sicuro che se apri Task Manager e uccidi il processo, finallynon verrà eseguito. O lo stesso se il computer si blocca prima: D
Alejandro

145
finallynon funzionerà se il cavo di alimentazione viene strappato dal muro.
user253751

3
Potresti essere interessato a questa risposta alla stessa domanda su C #: stackoverflow.com/a/10260233/88656
Eric Lippert

1
Bloccalo su un semaforo vuoto. Non segnalarlo mai. Fatto.
Martin James

Risposte:


206

"Garantito" è una parola molto più forte di qualsiasi implementazione di finallymerita. Ciò che viene garantito è che se l'esecuzione esce tutta try- finallycostrutto, passerà attraverso il finallyfarlo. Ciò che non è garantito è che l'esecuzione uscirà dal file try- finally.

  • Un finallyin un generatore o una coroutine asincrona potrebbe non essere mai eseguito , se l'oggetto non viene mai eseguito fino alla conclusione. Ci sono molti modi che potrebbero accadere; eccone uno:

    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.')
    

    Nota che questo esempio è un po 'complicato: quando il generatore viene raccolto dalla spazzatura, Python tenta di eseguire il finallyblocco lanciando GeneratorExitun'eccezione, ma qui catturiamo quell'eccezione e poi di yieldnuovo, a quel punto Python stampa un avviso ("generator ignored GeneratorExit ") e si arrende. Vedere PEP 342 (Coroutines via Enhanced Generators) per i dettagli.

    Altri modi di un generatore o coroutine potrebbero non eseguire a conclusione includono se l'oggetto è solo mai GC'ed (sì, che è possibile, anche in CPython), o se un async with awaits in __aexit__, o se l'oggetto awaits o yields in un finallyblocco. Questo elenco non vuole essere esaustivo.

  • Un finallythread in un daemon potrebbe non essere mai eseguito se tutti i thread non daemon terminano per primi.

  • os._exitinterromperà immediatamente il processo senza eseguire i finallyblocchi.

  • os.forkpuò far sì che i finallyblocchi vengano eseguiti due volte . Oltre ai normali problemi che ti aspetteresti da cose che accadono due volte, ciò potrebbe causare conflitti di accesso simultanei (arresti anomali, blocchi, ...) se l'accesso alle risorse condivise non è sincronizzato correttamente .

    Poiché multiprocessingutilizza fork-without-exec per creare processi di lavoro quando si utilizza il metodo fork start (il predefinito su Unix), quindi chiama os._exitil worker una volta che il lavoro del lavoratore è terminato, finallye l' multiprocessinginterazione può essere problematica ( esempio ).

  • Un errore di segmentazione di livello C impedirà l' finallyesecuzione dei blocchi.
  • kill -SIGKILLimpedirà l' finallyesecuzione dei blocchi. SIGTERMe SIGHUPimpedirà anche l' finallyesecuzione dei blocchi a meno che non si installi un gestore per controllare da soli lo spegnimento; per impostazione predefinita, Python non gestisce SIGTERMo SIGHUP.
  • Un'eccezione in finallypuò impedire il completamento della pulizia. Un caso particolarmente degno di nota è se l'utente preme control-C proprio mentre stiamo iniziando a eseguire il finallyblocco. Python solleverà un KeyboardInterrupte salterà ogni riga del finallycontenuto del blocco. (il KeyboardInterruptcodice -safe è molto difficile da scrivere).
  • Se il computer perde potenza o se si iberna e non si riattiva, i finallyblocchi non verranno eseguiti.

Il finallyblocco non è un sistema di transazione; non fornisce garanzie di atomicità o qualcosa del genere. Alcuni di questi esempi potrebbero sembrare ovvi, ma è facile dimenticare che cose del genere possono accadere e fare finallytroppo affidamento .


14
Credo che solo il primo punto della tua lista sia veramente rilevante, e c'è un modo semplice per evitarlo: 1) non usare mai un nudo excepte non catturare mai GeneratorExitun generatore. I punti su thread / uccisione del processo / segfault / spegnimento sono previsti, python non può fare magie. Inoltre: le eccezioni in finallysono ovviamente un problema, ma questo non cambia il fatto che il flusso di controllo è stato spostato nel finallyblocco. A questo proposito Ctrl+C, puoi aggiungere un gestore di segnali che lo ignori, o semplicemente "pianifica" un arresto pulito dopo che l'operazione corrente è stata completata.
Giacomo Alzetta

8
La menzione di kill -9 è tecnicamente corretta, ma un po 'ingiusta. Nessun programma scritto in nessuna lingua esegue alcun codice dopo aver ricevuto un kill -9. In effetti, nessun programma riceve mai un kill -9, quindi anche se lo volesse, non potrebbe eseguire nulla. Questo è il punto di uccidere -9.
Tom

10
@ Tom: il punto su kill -9non specificava una lingua. E francamente, deve essere ripetuto, perché si trova in un punto cieco. Troppe persone dimenticano, o non si rendono conto, che il loro programma potrebbe essere interrotto senza nemmeno essere autorizzato a ripulire.
cHao

5
@GiacomoAlzetta: Ci sono persone là fuori che fanno affidamento sui finallyblocchi come se fornissero garanzie transazionali. Potrebbe sembrare ovvio che non lo facciano, ma non è qualcosa che tutti capiscono. Per quanto riguarda il caso del generatore, ci sono molti modi in cui un generatore potrebbe non essere affatto GC, e molti modi in cui un generatore o una coroutine potrebbero accidentalmente cedere dopo GeneratorExitanche se non cattura il GeneratorExit, ad esempio se async withsospende una coroutine in __exit__.
user2357112 supporta Monica

2
@ user2357112 yeah - Ho provato per decenni a convincere gli sviluppatori a ripulire i file temporanei ecc. all'avvio dell'app, non a uscire. Affidarsi alla cosiddetta 'conclusione pulita e aggraziata', è chiedere delusione e lacrime :)
Martin James

68

Sì. Alla fine vince sempre.

L'unico modo per sconfiggerlo è interrompere l'esecuzione prima che ne finally:abbia la possibilità (es. Mandare in crash l'interprete, spegnere il computer, sospendere un generatore per sempre).

Immagino che ci siano altri scenari a cui non ho pensato.

Eccone un altro paio a cui potresti non aver pensato:

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'

A seconda di come esci dall'interprete, a volte puoi "annullare" finalmente, ma non in questo modo:

>>> import sys
>>> try:
...     sys.exit()
... finally:
...     print('finally wins!')
... 
finally wins!
$

Utilizzando il precario os._exit(questo rientra nella categoria "crash the interpreter" secondo me):

>>> import os
>>> try:
...     os._exit(1)
... finally:
...     print('finally!')
... 
$

Attualmente sto eseguendo questo codice, per verificare se alla fine verrà ancora eseguito dopo la morte termica dell'universo:

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

Tuttavia, sto ancora aspettando il risultato, quindi ricontrolla qui più tardi.


5
o avere un ciclo finito i in try catch
sapy


27
Dopo la morte per calore dell'universo il tempo cessa di esistere, quindi sleep(1)risulterebbe sicuramente in un comportamento non definito. MrGreen
David Foerster

Puoi citare _os.exit subito dopo "l'unico modo per sconfiggerlo è mandare in crash il compilatore". In questo momento sono esempi contrastanti in cui alla fine vince
Stevoisiak

2
@StevenVascellaro Non credo sia necessario - os._exitè, per tutti gli scopi pratici, lo stesso che indurre un crash (uscita impura). Il modo corretto per uscire è sys.exit.
wim

9

Secondo la documentazione di Python :

Indipendentemente da ciò che è accaduto in precedenza, il blocco finale viene eseguito una volta completato il blocco di codice e gestite eventuali eccezioni sollevate. Anche se c'è un errore in un gestore di eccezioni o nel blocco else e viene sollevata una nuova eccezione, il codice nel blocco finale viene comunque eseguito.

Va anche notato che se ci sono più istruzioni return, inclusa una nel blocco finalmente, allora il ritorno del blocco finalmente è l'unico che verrà eseguito.


8

Ebbene sì e no.

Ciò che è garantito è che Python proverà sempre ad eseguire il blocco finalmente. Nel caso in cui torni dal blocco o sollevi un'eccezione non rilevata, il blocco finalmente viene eseguito appena prima di restituire o sollevare effettivamente l'eccezione.

(cosa avresti potuto controllare da solo eseguendo semplicemente il codice nella tua domanda)

L'unico caso che posso immaginare in cui il blocco finalmente non verrà eseguito è quando lo stesso interpretatore Python si arresta in modo anomalo, ad esempio all'interno del codice C oa causa di un'interruzione di corrente.


ah ah .. o c'è un loop infinito in try catch
sapy

Penso che "Beh, sì e no" sia più corretto. Infine: vince sempre dove "sempre" significa che l'interprete è in grado di eseguire e il codice per "finalmente:" è ancora disponibile e "vince" è definito come l'interprete proverà a eseguire il blocco finalmente: e avrà successo. Questo è il "Sì" ed è molto condizionale. "No" sono tutti i modi in cui l'interprete potrebbe fermarsi prima di "finalmente": - interruzione di corrente, guasto hardware, kill -9 rivolto all'interprete, errori nell'interprete o codice da cui dipende, altri modi per sospendere l'interprete. E modi per appendere all'interno del "finalmente:".
Bill IV

1

Ho trovato questo senza usare una funzione del generatore:

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)

Il sonno può essere qualsiasi codice che potrebbe essere eseguito per periodi di tempo incoerenti.

Quello che sembra accadere qui è che il primo processo parallelo per finire lascia il blocco try con successo, ma poi tenta di restituire dalla funzione un valore (foo) che non è stato definito da nessuna parte, il che causa un'eccezione. Quell'eccezione uccide la mappa senza consentire agli altri processi di raggiungere i loro blocchi finali.

Inoltre, se aggiungi la riga bar = bazzsubito dopo la chiamata sleep () nel blocco try. Quindi il primo processo per raggiungere quella linea genera un'eccezione (perché bazz non è definito), che fa eseguire il proprio blocco finale, ma poi uccide la mappa, facendo scomparire gli altri blocchi di prova senza raggiungere i blocchi finali, e anche il primo processo a non raggiungere la sua istruzione return.

Ciò che questo significa per il multiprocessing Python è che non puoi fidarti del meccanismo di gestione delle eccezioni per ripulire le risorse in tutti i processi se anche uno dei processi può avere un'eccezione. Sarebbe necessaria una gestione aggiuntiva del segnale o la gestione delle risorse al di fuori della chiamata della mappa multiprocessing.


-2

Addendum alla risposta accettata, solo per aiutare a vedere come funziona, con alcuni esempi:

  • Questo:

     try:
         1
     except:
         print 'except'
     finally:
         print 'finally'
    

    verrà prodotto

    finalmente

  •    try:
           1/0
       except:
           print 'except'
       finally:
           print 'finally'

    verrà prodotto

    tranne
    finalmente


2
Questo non risponde affatto alla domanda. Ha solo esempi di quando funziona .
zixuan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.