Meglio "provare" qualcosa e rilevare l'eccezione o verificare se è possibile prima evitare un'eccezione?


139

Devo testare ifqualcosa di valido o semplicemente tryfarlo e catturare l'eccezione?

  • Esiste una solida documentazione che dice che è preferibile un modo?
  • È un modo più pitonico ?

Ad esempio, dovrei:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

O:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Alcuni pensieri ...
PEP 20 dice:

Gli errori non dovrebbero mai passare silenziosamente.
A meno che non sia esplicitamente messo a tacere.

L'uso di a tryinvece di an ifdovrebbe essere interpretato come un errore che passa silenziosamente? E se è così, lo stai esplicitamente mettendo a tacere usandolo in questo modo, quindi rendendolo OK?


Non mi riferisco a situazioni in cui puoi fare le cose solo 1 modo; per esempio:

try:
    import foo
except ImportError:
    import baz

Risposte:


161

Si dovrebbe preferire try/exceptsopra if/elsese che si traduce in

  • accelerazioni (ad esempio impedendo ricerche extra)
  • codice più pulito (meno righe / più facile da leggere)

Spesso, questi vanno di pari passo.


speed-up

Nel caso di provare a trovare un elemento in un lungo elenco:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

il tentativo, tranne è l'opzione migliore quando indexprobabilmente è nell'elenco e IndexError di solito non viene generato. In questo modo si evita la necessità di una ricerca aggiuntivaif index < len(my_list) .

Python incoraggia l'uso di eccezioni, che gestisci è una frase di Dive Into Python . Il tuo esempio non solo gestisce l'eccezione (con garbo), piuttosto che lasciarlo passare silenziosamente , anche l'eccezione si verifica solo nel caso eccezionale di indice non trovato (da cui la parola eccezione !).


codice più pulito

La documentazione ufficiale di Python menziona EAFP : è più facile chiedere perdono che permesso e Rob Knight osserva che la cattura di errori piuttosto che evitarli può portare a codice più pulito e più facile da leggere. Il suo esempio lo dice così:

Peggio ancora (LBYL "guarda prima di saltare") :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Meglio (EAFP: più facile chiedere perdono che permesso) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None

if index in mylistverifica se l'indice è un elemento della mia lista, non un possibile indice. Vorresti if index < len(mylist)invece.
ychaouche,

1
Questo ha senso, ma dove trovo la documentazione che chiarisce quali possibili eccezioni possono essere generate per int (). docs.python.org/3/library/functions.html#int Non riesco a trovare queste informazioni qui.
BrutalSimplicity

Al contrario, puoi aggiungere questo caso (da off. Doc ) alla tua risposta, quando dovresti preferire if/elseditry/catch
rappongy

20

In questo caso particolare, dovresti usare qualcos'altro interamente:

x = myDict.get("ABC", "NO_ABC")

In generale, tuttavia: se si prevede che il test fallisca frequentemente, utilizzare if. Se il test è costoso relativamente al solo tentativo di operazione e alla rilevazione dell'eccezione in caso di errore, utilizzare try. Se nessuna di queste condizioni si applica, procedi con le letture più semplici.


1
+1 per la spiegazione sotto l'esempio di codice, che è perfetto.
ktdrv,

4
Penso che sia abbastanza chiaro che non è quello che stava chiedendo, e ora ha modificato il post per renderlo ancora più chiaro.
AGF

9

L'uso trye exceptdirettamente anziché all'interno di una ifguardia dovrebbe sempre essere fatto se esiste la possibilità di una condizione di gara. Ad esempio, se si desidera assicurarsi che esista una directory, non procedere come segue:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Se un altro thread o processo crea la directory tra isdire mkdir, uscirai. Invece, fai questo:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Ciò uscirà solo se non è possibile creare la directory 'pippo'.


7

Se è banale controllare se qualcosa fallirà prima di farlo, probabilmente dovresti favorirlo. Dopotutto, la costruzione di eccezioni (inclusi i loro traceback associati) richiede tempo.

Le eccezioni dovrebbero essere utilizzate per:

  1. cose inaspettate, o ...
  2. cose in cui è necessario saltare più di un livello di logica (ad es. dove a break non ti porta abbastanza lontano), o ...
  3. cose in cui non sai esattamente cosa gestirà l'eccezione in anticipo, o ...
  4. cose in cui il controllo anticipato del guasto è costoso (relativamente al solo tentativo di operazione)

Si noti che spesso, la vera risposta è "nè" - per esempio, nel primo esempio, ciò che veramente dovrebbe fare è semplicemente usare .get()per fornire un default:

x = myDict.get('ABC', 'NO_ABC')

tranne che in if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'realtà è spesso più veloce dell'uso get, sfortunatamente. Non dire che questo è il criterio più importante, ma è qualcosa di cui essere consapevoli.
AGF

@agf: meglio scrivere codice chiaro e conciso. Se qualcosa deve essere ottimizzato in un secondo momento, è facile tornare indietro e riscriverlo, ma c2.com/cgi/wiki?PrematureOptimization
Amber

Lo so ; il mio punto era quello if / elsee try / exceptpuò avere il loro posto anche quando ci sono alternative specifiche del caso perché hanno caratteristiche prestazionali diverse.
AGF

@agf, sai se il metodo get () sarà migliorato nelle versioni future per essere (almeno) veloce come cercare esplicitamente? A proposito, quando si guarda due volte (come in 'ABC' in d: d ['ABC']), si prova: d ['ABC'] tranne KeyError: ... non il più veloce?
Remi,

2
@Remi La parte lenta di .get()è la ricerca degli attributi e l'overhead della chiamata di funzione a livello di Python; l'uso delle parole chiave negli incorporamenti va praticamente a C. Non penso che presto diventerà troppo veloce. Per quanto riguarda ifvs. try, il metodo read dict.get () restituisce un puntatore con alcune informazioni sulle prestazioni. Il rapporto tra hit e miss è importante ( trypuò essere più veloce se la chiave esiste quasi sempre) così come le dimensioni del dizionario.
AGF

5

Come menzionano gli altri post, dipende dalla situazione. Esistono alcuni pericoli legati all'uso di try / tranne che al posto di verificare in anticipo la validità dei dati, in particolare se utilizzati su progetti più grandi.

  • Il codice nel blocco try potrebbe avere la possibilità di creare ogni sorta di caos prima che venga rilevata l'eccezione - se controlli preventivamente in anticipo con un'istruzione if puoi evitarlo.
  • Se il codice chiamato nel tuo blocco try genera un tipo di eccezione comune, come TypeError o ValueError, potresti non catturare effettivamente la stessa eccezione che ti aspettavi di catturare - potrebbe essere qualcos'altro che solleva la stessa classe di eccezione prima o dopo anche arrivare a la linea in cui può essere sollevata la tua eccezione.

ad esempio, supponiamo che tu abbia:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError non dice nulla sul fatto che si sia verificato durante il tentativo di ottenere un elemento di index_list o my_list.


4

L'uso di un tentativo invece di un se deve essere interpretato come un errore che passa silenziosamente? E se è così, lo stai esplicitamente mettendo a tacere usandolo in questo modo, quindi rendendolo OK?

L'uso tryè riconoscere che può passare un errore, il che è l'opposto del farlo passare silenziosamente. L'uso exceptsta facendo sì che non passi affatto.

L'uso try: except:è preferito nei casi in cui la if: else:logica è più complicata. Semplice è meglio di complesso; il complesso è meglio del complicato; ed è più facile chiedere perdono che permesso.

Quali "errori non dovrebbero mai passare silenziosamente" è un avvertimento, è il caso in cui il codice potrebbe sollevare un'eccezione di cui sei a conoscenza e in cui il tuo progetto ammette la possibilità, ma non hai progettato in modo da gestire l'eccezione. A mio avviso, esplicitamente mettere a tacere un errore farebbe qualcosa del genere passin unexcept blocco, il che dovrebbe essere fatto solo con la consapevolezza che "non fare nulla" è davvero la corretta gestione degli errori in una situazione particolare. (Questa è una delle poche volte in cui mi sento come se fosse davvero necessario un commento in un codice ben scritto.)

Tuttavia, nel tuo esempio particolare, nessuno dei due è appropriato:

x = myDict.get('ABC', 'NO_ABC')

Il motivo per cui tutti lo stanno sottolineando - anche se riconosci il tuo desiderio di capire in generale e l'incapacità di fornire un esempio migliore - è che in molti casi esistono effettivamente passi secondari equivalenti, e cercarli è il primo passo per risolvere il problema.


3

Ogni volta che usi try/exceptper il flusso di controllo, chiediti:

  1. È facile vedere quando try blocco ha esito positivo e quando non riesce?
  2. Sei a conoscenza di tutti gli effetti collaterali all'interno ditry blocco?
  3. Sei a conoscenza di tutti i casi in cui iltry blocco genera l'eccezione?
  4. Se l'implementazione del tryblocco cambia, il flusso di controllo si comporterà comunque come previsto?

Se la risposta a una o più di queste domande è "no", potrebbe esserci molto perdono da chiedere; molto probabilmente dal tuo io futuro.


Un esempio. Recentemente ho visto il codice in un progetto più grande che assomigliava a questo:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

Parlando con il programmatore, è emerso che il flusso di controllo previsto era:

Se x è un numero intero, y = foo (x).

Se x è un elenco di numeri interi, y = bar (x).

Ciò ha funzionato perché ha foocreato una query nel database e la query avrebbe avuto esito positivo se xfosse un numero intero e avrebbe generato un ProgrammingErrorse xfosse un elenco.

L'uso try/exceptè una cattiva scelta qui:

  1. Il nome dell'eccezione, ProgrammingErrornon dà via il problema reale (che xnon è un numero intero), il che rende difficile vedere cosa sta succedendo.
  2. Il ProgrammingErrorviene sollevata nel corso di una chiamata di database, che fa perdere tempo. Le cose diventerebbero davvero orribili se si scoprisse che fooscrive qualcosa nel database prima che generi un'eccezione o che alteri lo stato di qualche altro sistema.
  3. Non è chiaro se ProgrammingErrorviene generato solo quando xè presente un elenco di numeri interi. Supponiamo ad esempio che ci sia un refuso nella fooquery del database. Questo potrebbe anche sollevare a ProgrammingError. La conseguenza è che bar(x)ora viene anche chiamato quando xè un numero intero. Ciò potrebbe sollevare eccezioni criptiche o produrre risultati imprevedibili.
  4. Il try/exceptblocco aggiunge un requisito a tutte le future implementazioni di foo. Ogni volta che cambiamo foo, ora dobbiamo pensare a come gestisce gli elenchi e assicurarci che emetta un errore ProgrammingErrore non, per esempio, un AttributeErrorerrore o nessun errore.

0

Per un significato generale, potresti prendere in considerazione la lettura di Idiomi e Anti-Idiomi in Python: Exceptions .

Nel tuo caso particolare, come altri hanno affermato, dovresti usare dict.get():

get (chiave [, impostazione predefinita])

Restituisce il valore per chiave se chiave è nel dizionario, altrimenti impostazione predefinita. Se non viene fornito il valore predefinito, il valore predefinito è Nessuno, quindi questo metodo non genera mai un KeyError.


Non credo che il link copra affatto questa situazione. I suoi esempi riguardano la gestione di cose che rappresentano errori reali , non se utilizzare la gestione delle eccezioni per le situazioni previste.
AGF

Il primo link è obsoleto.
Timofei Bondarev,
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.