2010-06-21 7 views
15

È stato detto in un paio di posti (here e here) che l'enfasi di Python su "è più facile chiedere perdono che permesso" (EAFP) dovrebbe essere mitigata dall'idea che le eccezioni dovrebbero essere chiamate solo in casi veramente eccezionali. Si consideri il seguente, in cui stiamo popping e spingendo su una coda di priorità fino a quando un solo elemento è lasciato:Eccezioni Python: EAFP e cosa è davvero eccezionale?

import heapq 
... 
pq = a_list[:] 
heapq.heapify(pq) 
while True: 
    min1 = heapq.heappop(pq) 
    try: 
     min2 = heapq.heappop(pq) 
    except IndexError: 
     break 
    else 
     heapq.heappush(pq, min1 + min2) 
# do something with min1 

L'eccezione viene sollevata solo una volta in len(a_list) iterazioni del ciclo, ma non è davvero eccezionale, perché sappiamo che alla fine succederà. Questa configurazione ci impedisce di verificare se a_list è vuoto un sacco di volte, ma (forse) è meno leggibile rispetto all'utilizzo di condizioni esplicite.

Qual è il consenso sull'uso di eccezioni per questo tipo di logica di programma non eccezionale?

+0

Il tuo codice mi sembra strano. 'heapify' trasforma un elenco * in posizione * in un heap. Come risultato di 'pq = heapq.heapify (a_list)', 'pq' sarà' None'. – stephan

+1

Hai ragione! Errore stupido che ho fatto quando ho cercato di distillare il mio codice reale in qualcosa di adatto per un esempio. È riparato ora. –

risposta

28

exceptions should only be called in truly exceptional cases

Non in Python: per esempio, ognifor loop (meno prematuramente break s o return s) termina da un'eccezione (StopIteration) essendo gettati e catturati. Quindi, un'eccezione che si verifica una volta per ciclo non è affatto strana per Python - è lì il più delle volte!

Il principio in questione può essere cruciale in altre lingue, ma non è assolutamente un motivo per applicare tale principio a Python, dove è così contrario all'etica della lingua.

In questo caso mi piace la riscrittura di Jon (che dovrebbe essere ulteriormente semplificata rimuovendo il ramo else) perché rende il codice più compatto - una ragione pragmatica, sicuramente non il "tempering" dello stile Python con un principio alieno .

+0

Buon punto sulla semplificazione Alex - fatto! –

+0

Con EAFP, come si evitano i bug silenziosi causati dall'aver rilevato un'eccezione diversa da quella prevista? Ad esempio: 'prova: r = f (x.a) tranne AttributeError: r = 0'. Pensavate di assegnare solo 'r = 0' quando' x' non ha attributo 'a'. Ma succede solo che una condizione interna in 'f' ha gettato la stessa eccezione, e tu l'hai catturata inconsapevolmente. Devo davvero dividere ogni espressione in parti atomiche, prima di inserire uno di questi segmenti in 'try' /' except'? – max

+0

Max, penso che l'approccio migliore in questo caso sarebbe quello di aggiungere un blocco 'try/accept' per funzionare' f' per catturare l'attributo internamente 'AttributeError' prima che raggiunga il gestore esterno. –

6

Guardando the docs Penso che si può tranquillamente riscrivere la funzione come segue:

import heapq 
... 
pq = heapq.heapify(a_list) 
while pq: 
    min1 = heapq.heappop(pq) 
    if pq: 
     min2 = heapq.heappop(pq) 
     heapq.heappush(pq, min1 + min2) 
# do something with min1 

..e quindi evitare il try-except.

Arrivare alla fine di una lista che è qualcosa che sai succederà qui non è eccezionale - è offerto! Quindi una pratica migliore sarebbe gestirla in anticipo. Se tu avessi qualcos'altro in un altro thread che stava consumando dallo stesso heap, allora usando try-except avrebbe molto più senso (cioè gestire un caso speciale/imprevedibile).

Più in generale, eviterei di provare le eccezioni ovunque posso verificare ed evitare un errore in anticipo. Questo ti costringe a dire "So che questa brutta situazione potrebbe accadere, ecco come la gestisco". Secondo me, tenderai a scrivere un codice più leggibile come risultato.

[Edit] Aggiornato l'esempio come da suggerimento di Alex

3

ho trovato la pratica di usare come strumenti di controllo di flusso "normali" le eccezioni di essere abbastanza ampiamente accettato in Python. È più comunemente usato in situazioni come quella che descrivi, quando arrivi alla fine di una sorta di sequenza.

A mio parere, è un uso perfettamente valido di un'eccezione. Volete essere cauti nell'usare la gestione delle eccezioni, volenti o nolenti. Alzare un'eccezione è un'operazione ragionevolmente costosa, quindi è meglio assicurarsi di affidarsi solo a un'eccezione alla fine della sequenza, non in ogni iterazione.

8

Le eccezioni di lancio sono costose nella maggior parte dei linguaggi di basso livello come C++. Ciò influenza molto la "saggezza comune" sulle eccezioni e non si applica tanto alle lingue che girano in una VM, come Python. Non c'è un costo così grande in Python per l'utilizzo di un'eccezione anziché di un condizionale.

(Questo è un caso in cui la "saggezza comune" diventa una questione di abitudine.Le persone provengono dall'esperienza in un tipo di ambiente - linguaggi di basso livello - e quindi la applicano a nuovi domini senza valutare se ha senso).

Le eccezioni sono ancora, in generale, eccezionali. Ciò non significa che non accadano spesso; significa che sono l'eccezione. Sono le cose che tendono ad uscire dal flusso di codice ordinario e che la maggior parte delle volte non si vuole gestire una per una, che è il punto di gestione delle eccezioni. Questa parte è la stessa in Python come in C++ e in tutte le altre lingue con eccezioni.

Tuttavia, questo tende a definire quando le eccezioni sono generate. Stai parlando di quando le eccezioni dovrebbero essere catturate. Molto semplicemente, non preoccuparti: le eccezioni non sono costose, quindi non andare troppo per cercare di evitare che vengano lanciate. Un sacco di codice Python è progettato attorno a questo.

Non sono d'accordo con il suggerimento di Jon di provare a testare ed evitare eccezioni in anticipo. Va bene se porta a un codice più chiaro, come nel suo esempio. Tuttavia, in molti casi complicherà le cose - può effettivamente portare a duplicare assegni e introdurre bug. Ad esempio,

import os, errno, stat 

def read_file(fn): 
    """ 
    Read a file and return its contents. If the file doesn't exist or 
    can't be read, return "". 
    """ 
    try: 
     return open(fn).read() 
    except IOError, e: 
     return "" 

def read_file_2(fn): 
    """ 
    Read a file and return its contents. If the file doesn't exist or 
    can't be read, return "". 
    """ 
    if not os.access(fn, os.R_OK): 
     return "" 
    st = os.stat(fn) 
    if stat.S_ISDIR(st.st_mode): 
     return "" 
    return open(fn).read() 

print read_file("x") 

Certo, possiamo testare ed evitare il fallimento - ma abbiamo complicato le cose male. Stiamo cercando di indovinare tutti i modi in cui l'accesso ai file potrebbe non riuscire (e questo non li cattura tutti), potremmo aver introdotto condizioni di gara e stiamo facendo molto più lavoro di I/O. Questo è tutto fatto per noi - basta prendere l'eccezione.

4

Solo per la cronaca, mi piacerebbe scrivere è come questo:

import heapq 
a_list = range(20) 
pq = a_list[:] 
heapq.heapify(pq) 
try: 
    while True: 
     min1 = heapq.heappop(pq) 
     min2 = heapq.heappop(pq) 
     heapq.heappush(pq, min1 + min2) 
except IndexError: 
    pass # we ran out of numbers in pq 

eccezioni possono lasciare un loop (anche le funzioni) e si possono utilizzare per questo. Dal momento che Python li lancia ovunque, penso che questo modello sia abbastanza utile (anche pythonic).