Come pulire nested try / except / else?

6

Durante la scrittura del codice, spesso desidero fare qualcosa del genere:

try:
    foo()
except FooError:
    handle_foo()
else:
    try:
        bar()
    except BarError:
        handle_bar()
    else:
        try:
            baz()
        except BazError:
            handle_baz()
        else:
            qux()
finally:
    cleanup()

Ovviamente, questo è completamente illeggibile. Ma sta esprimendo un'idea relativamente semplice: eseguire una serie di funzioni (o snippet di codice breve), con un gestore di eccezioni per ciascuna, e fermarsi non appena una funzione fallisce. Immagino che Python possa fornire zucchero sintattico per questo codice, forse qualcosa del genere:

# NB: This is *not* valid Python
try:
    foo()
except FooError:
    handle_foo()
    # GOTO finally block
else try:
    bar()
except BarError:
    handle_bar()
    # ditto
else try:
    baz()
except BazError:
    handle_baz()
    # ditto
else:
    qux()
finally:
    cleanup()

Se non vengono sollevate eccezioni, questo equivale a foo();bar();baz();qux();cleanup() . Se vengono sollevate eccezioni, vengono gestite dal gestore delle eccezioni appropriato (se presente) e saltiamo su cleanup() . In particolare, se bar() genera un FooError o BazError , l'eccezione non viene catturata e si propagherà al chiamante. Questo è auspicabile, quindi rileviamo solo eccezioni che ci aspettiamo veramente di gestire.

Indipendentemente dalla bruttezza sintattica, questo tipo di codice è solo una cattiva idea in generale? Se sì, come lo rifattori? Immagino che i context manager possano essere usati per assorbire parte della complessità, ma non capisco davvero come funzionerebbe nel caso generale.

    
posta Kevin 17.10.2014 - 00:42
fonte

4 risposte

7
try:
    foo()
except FooError:
    handle_foo()
else:
    ...
finally:
    cleanup()

Che cosa fa handle_foo ? Ci sono alcune cose che facciamo tipicamente nei blocchi di gestione delle eccezioni.

  1. Pulizia dopo l'errore: ma in questo caso, foo () dovrebbe ripulire se stesso, non lasciarci fare così. Inoltre, la maggior parte dei lavori di pulizia viene gestita meglio con with
  2. Recupera il percorso felice: ma non lo fai perché non continui con il resto delle funzioni.
  3. Traduce il tipo di eccezione: ma non stai lanciando un'altra eccezione
  4. registra l'errore: ma non dovrebbe esserci alcuna necessità di avere blocchi di eccezioni speciali per ogni tipo.

Mi sembra che tu stia facendo qualcosa di strano nella tua gestione delle eccezioni. La tua domanda qui è un sintomo semplice sull'uso di eccezioni in un modo insolito. Non stai cadendo nel tipico schema, ed è per questo che è diventato imbarazzante.

Senza un'idea migliore di ciò che stai facendo in quelle funzioni di handle_ è tutto ciò che posso dire.

    
risposta data 17.10.2014 - 05:49
fonte
3

Sembra che tu abbia una sequenza di comandi che possono generare un'eccezione che deve essere gestita prima di tornare. Prova a raggruppare il codice e la gestione delle eccezioni in posizioni separate. Credo che questo faccia ciò che intendi.

try:
    foo()
    bar()
    baz()
    qux()

except FooError:
    handle_foo()
except BarError:
    handle_bar()
except BazError:
    handle_baz()

finally:
    cleanup()
    
risposta data 17.10.2014 - 03:47
fonte
0

Ci sono un paio di modi diversi, a seconda di cosa ti serve.

Ecco un modo con i loop:

try:
    for func, error, err_handler in (
            (foo, FooError, handle_foo),
            (bar, BarError, handle_bar),
            (baz, BazError, handle_baz),
        ):
        try:
            func()
        except error:
            err_handler()
            break
finally:
    cleanup()

Ecco un modo con un'uscita dopo error_handler:

def some_func():
    try:
        try:
            foo()
        except FooError:
            handle_foo()
            return
        try:
            bar()
        except BarError:
            handle_bar()
            return
        try:
            baz()
        except BazError:
            handle_baz()
            return
        else:
            qux()
    finally:
        cleanup()

Personalmente ritengo che la versione del loop sia più facile da leggere.

    
risposta data 17.10.2014 - 01:08
fonte
0

Innanzitutto, l'uso appropriato di with può spesso ridurre o addirittura eliminare un sacco di codice di gestione delle eccezioni, migliorando sia la manutenibilità che la leggibilità.

Ora puoi ridurre il nidificazione in molti modi; altri poster ne hanno già forniti alcuni, quindi ecco la mia variante:

for _ in range(1):
    try:
        foo()
    except FooError:
        handle_foo()
        break
    try:
        bar()
    except BarError:
        handle_bar()
        break
    try:
        baz()
    except BazError:
        handle_baz()
        break
    qux()
cleanup()
    
risposta data 17.10.2014 - 05:14
fonte

Leggi altre domande sui tag