Utilizzo di try vs if in Python


145

C'è una logica per decidere quale di tryoif costrutto usare, quando si testa una variabile per avere un valore?

Ad esempio, esiste una funzione che restituisce un elenco o non restituisce un valore. Voglio verificare il risultato prima di elaborarlo. Quale delle seguenti sarebbe più preferibile e perché?

result = function();
if (result):
    for r in result:
        #process items

o

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

Discussione correlata:

Verifica dell'esistenza dei membri in Python


Tieni presente che se la tua stanza #process items è suscettibile di lanciare un TypeError devi avvolgerti in un altro tentativo: tranne: block. Per questo esempio specifico
userei

Risposte:


237

Senti spesso che Python incoraggia lo stile EAFP ("è più facile chiedere perdono che permesso") rispetto allo stile LBYL ("guarda prima di saltare"). Per me è una questione di efficienza e leggibilità.

Nel tuo esempio (dire che invece di restituire un elenco o una stringa vuota, la funzione doveva restituire un elenco o None), se ti aspetti che il 99% delle volte resultconterrà effettivamente qualcosa di iterabile, utilizzerei l' try/exceptapproccio. Sarà più veloce se le eccezioni sono davvero eccezionali. Se resultè Nonepiù del 50% delle volte, l'utilizzo ifè probabilmente migliore.

Per supportare questo con alcune misure:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Quindi, mentre ifun'affermazione ti costa sempre , è quasi gratuito impostare un try/exceptblocco. Ma quando si Exceptionverifica effettivamente, il costo è molto più elevato.

Morale:

  • È perfettamente OK (e "pythonic") da utilizzare try/exceptper il controllo del flusso,
  • ma ha senso soprattutto quando Exceptions sono in realtà eccezionali.

Dai documenti di Python:

EAFP

È più facile chiedere perdono che permesso. Questo stile di codifica Python comune presuppone l'esistenza di chiavi o attributi validi e rileva eccezioni se l'assunzione risulta falsa. Questo stile pulito e veloce è caratterizzato dalla presenza di molte trye exceptaffermazioni. La tecnica contrasta con lo stile LBYL comune a molte altre lingue come C.


1
Grazie. Vedo che in questo caso la logica può essere l'aspettativa del risultato.
artdanil,

6
.... e questo è uno (dei tanti) motivi per cui è così difficile realizzare un JIT per Python davvero ottimizzante. Come dimostra LuaJIT 2 recentemente in beta, i linguaggi dinamici possono essere molto, molto veloci; ma dipende fortemente dal design del linguaggio iniziale e dallo stile che incoraggia. (in una nota correlata, il design del linguaggio è il motivo per cui anche i migliori JIT JavaScirpt non possono essere paragonati a LuaJIT 1, tanto meno 2)
Javier

1
@ 2rs2ts: ho fatto anch'io dei tempi simili. In Python 3 try/exceptera il 25% più veloce rispetto if key in d:ai casi in cui la chiave era nel dizionario. Era molto più lento quando la chiave non era nel dizionario, come previsto, e coerente con questa risposta.
Tim Pietzcker,

5
So che questa è una vecchia risposta, tuttavia: usare istruzioni come 1/1con timeit non è un'ottima scelta, perché saranno ottimizzate (vedi dis.dis('1/1')e nota che non si verifica alcuna divisione).
Andrea Corbellini,

1
Dipende anche dall'operazione che sei disposto a eseguire. Una singola divisione è veloce, rilevare un errore è piuttosto costoso. Ma anche nei casi in cui il costo della gestione delle eccezioni è accettabile, pensa attentamente a ciò che stai chiedendo al computer di fare. Se l'operazione stessa è molto costosa indipendentemente dai suoi risultati, e c'è un modo economico per capire se il successo è possibile: Usalo. Esempio: non copiare metà del file solo per rendersi conto che non c'è abbastanza spazio.
Bachsau,

14

La tua funzione non dovrebbe restituire tipi misti (es. Lista o stringa vuota). Dovrebbe restituire un elenco di valori o solo un elenco vuoto. Quindi non dovrai testare nulla, ad esempio il tuo codice collassa per:

for r in function():
    # process items

2
Sono completamente d'accordo con te su questo punto; tuttavia, la funzione non è mia e la sto solo usando.
artdanil,

2
@artdanil: Quindi potresti racchiudere quella funzione in quella che scrivi che funziona come quella a cui sta pensando Brandon Corfman.
Quamrana,

24
che pone la domanda: il wrapper dovrebbe usare un if o un tentativo di gestire il caso non iterabile?
jcdyer,

@quamrana che è esattamente quello che sto cercando di fare. Come sottolineato da @jcd, la domanda è su come dovrei gestire la situazione nella funzione wrapper.
artdanil,

12

Ignora la mia soluzione se il codice che fornisco non è ovvio a prima vista e devi leggere la spiegazione dopo l'esempio di codice.

Posso presumere che "nessun valore restituito" significhi che il valore restituito è Nessuno? Se sì, o se "nessun valore" è falso in senso booleano, puoi fare quanto segue, poiché il tuo codice tratta essenzialmente "nessun valore" come "non iterare":

for r in function() or ():
    # process items

Se function()restituisce qualcosa che non è Vero, si scorre sopra la tupla vuota, cioè non si esegue alcuna iterazione. Questo è essenzialmente LBYL.


4

Il tuo secondo esempio è rotto: il codice non genererà mai un'eccezione TypeError poiché puoi scorrere sia le stringhe che gli elenchi. È anche valido scorrere una stringa o un elenco vuoti: eseguirà il corpo del ciclo zero volte.


4

Quale delle seguenti sarebbe più preferibile e perché?

In questo caso è preferibile guardare prima di saltare. Con l'approccio dell'eccezione, un TypeError potrebbe verificarsi in qualsiasi punto del corpo del loop e verrebbe catturato e gettato via, il che non è ciò che si desidera e renderebbe difficile il debug.

(Sono d'accordo con Brandon Corfman: la restituzione di None per "nessun elemento" invece di un elenco vuoto è interrotta. È un'abitudine spiacevole per i programmatori Java che non dovrebbe essere vista in Python. O Java.)


4

In generale, l'impressione che ho avuto è che le eccezioni dovrebbero essere riservate a circostanze eccezionali. Se resultci si aspetta che non sia mai vuoto (ma potrebbe essere, se, ad esempio, un disco si è bloccato, ecc.), Il secondo approccio ha senso. Se, d'altra parte, un vuoto resultè perfettamente ragionevole in condizioni normali, testarlo con unif dichiarazione ha più senso.

Avevo in mente lo scenario (più comune):

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

invece dell'equivalente:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

Questo è un esempio della differenza tra gli approcci menzionati da Tim Pietzcker: il primo è LBYL, il secondo è EAFP
Managu

Solo una breve nota che ++non funziona in Python, usa += 1invece.
Tgray,

3

bobince sottolinea saggiamente che avvolgere il secondo caso può anche catturare TypeErrors nel loop, che non è quello che vuoi. Se vuoi davvero usare un tentativo, puoi testare se è iterabile prima del ciclo

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

Come puoi vedere, è piuttosto brutto. Non lo consiglio, ma dovrebbe essere menzionato per completezza.


Ai miei occhi, imbattersi in errori di tipo intenzionalmente è sempre un cattivo stile di codifica. Uno sviluppatore dovrebbe sapere cosa aspettarsi ed essere pronto a gestire tali valori senza eccezioni. Ogni volta che un'operazione ha fatto quello che doveva fare (dal punto di vista del programmatore) e i risultati previsti sono un elenco o None, controllare sempre il risultato con is Noneo is not None. D'altra parte, è anche uno stile negativo sollevare eccezioni per risultati legittimi. Le eccezioni sono per le cose inaspettate. Esempio: str.find()restituisce -1 se non viene trovato nulla, poiché la ricerca stessa è stata completata senza errori.
Bachsau,

1

Per quanto riguarda le prestazioni, l'utilizzo di try block per il codice che normalmente non genera eccezioni è più veloce dell'utilizzo dell'istruzione if ogni volta. Quindi, la decisione dipende dalla probabilità di casi eccezionali.


-5

Come regola generale, non si dovrebbe mai usare try / catch o qualsiasi altra eccezione che gestisca roba per controllare il flusso. Anche se l'iterazione dietro le quinte è controllata dall'aumento delle StopIterationeccezioni, dovresti comunque preferire il tuo primo frammento di codice al secondo.


7
Questo è vero in Java. Python abbraccia EAFP abbastanza pesantemente.
fengb,

3
È ancora una pratica strana. Nella mia esperienza, il cervello della maggior parte dei programmatori è cablato per saltare EAFP. Finiscono per chiedersi perché sia ​​stato selezionato un determinato percorso. Detto questo, c'è un tempo e un luogo per infrangere ogni regola.
ilowe,
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.