Python: come posso sapere quali eccezioni potrebbero essere generate da una chiamata al metodo


89

C'è un modo per sapere (al momento della codifica) quali eccezioni aspettarsi durante l'esecuzione di codice Python? Finisco per catturare la classe di eccezione di base il 90% delle volte poiché non so quale tipo di eccezione potrebbe essere generato (e non dirmi di leggere la documentazione. Molte volte un'eccezione può essere propagata dal profondo. E molti volte la documentazione non è aggiornata o corretta). C'è qualche tipo di strumento per verificarlo? (come leggendo il codice Python e le librerie)?


2
Tieni presente che in Python <2.6, puoi anche raisestringhe, non solo BaseExceptionsottoclassi. Quindi, se stai chiamando il codice della libreria che è fuori dal tuo controllo, anche except Exceptionse non è sufficiente, poiché non cattura eccezioni di stringa. Come altri hanno sottolineato, qui stai abbaiando contro l'albero sbagliato.
Daniel Pryden

Non lo sapevo. Ho pensato tranne Eccezione: .. cattura quasi tutto.
GabiMe

2
except Exceptionfunziona bene per rilevare le eccezioni di stringa in Python 2.6 e versioni successive.
Jeffrey Harris

Risposte:


22

Immagino che una soluzione potrebbe essere solo imprecisa a causa della mancanza di regole di digitazione statiche.

Non sono a conoscenza di uno strumento che controlla le eccezioni, ma potresti trovare uno strumento personalizzato che corrisponda alle tue esigenze (una buona possibilità per giocare un po 'con l'analisi statica).

Come primo tentativo, potresti scrivere una funzione che costruisce un AST, trova tutto Raise nodi e quindi cerca di capire modelli comuni di generazione di eccezioni (ad esempio chiamando direttamente un costruttore)

Sia xil seguente programma:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

Costruisci l'AST utilizzando il compilerpacchetto:

tree = compiler.parse(x)

Quindi definisci una Raiseclasse visitatore:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

E percorri i Raisenodi di raccolta AST :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

Puoi continuare risolvendo i simboli usando le tabelle dei simboli del compilatore, analizzando le dipendenze dei dati, ecc. Oppure puoi semplicemente dedurre che CallFunc(Name('IOError'), ...)"dovrebbe sicuramente significare aumentare IOError", il che è abbastanza OK per risultati pratici rapidi :)


Grazie per questa interessante risposta. Non capivo però perché dovrei cercare qualcosa di più diverso da tutti i nodi di rilancio. Perché dovrei "risolvere i simboli utilizzando le tabelle dei simboli del compilatore, analizzando le dipendenze dei dati"? L'unico modo per sollevare un'eccezione non è aumentare ()?
GabiMe

1
Dato il v.nodesvalore di cui sopra, non puoi effettivamente dire qual è la cosa Name('IOError')o Name('e'). Tu non sai che valore (s) quelli IOErrore edi puntare, in quanto sono variabili cosiddette libere. Anche se il loro contesto di associazione fosse noto (qui entrano in gioco le tabelle dei simboli), dovresti eseguire una sorta di analisi della dipendenza dai dati per dedurre i loro valori esatti (questo dovrebbe essere difficile in Python).
Andrey Vlasovskikh

Poiché stai cercando una pratica soluzione semiautomatica, un elenco di ['IOError(errno.ENOENT, "not found")', 'e']visualizzati all'utente va bene. Ma non è possibile dedurre classi effettive di valori delle variabili rappresentate da stringhe :) (scusa per il ripubblicazione)
Andrey Vlasovskikh

1
Sì. Questo metodo, sebbene intelligente, in realtà non ti dà una copertura completa. A causa della natura dinamica di Python, è perfettamente possibile (anche se ovviamente una cattiva idea) che il codice a cui stai chiamando faccia qualcosa di simile exc_class = raw_input(); exec "raise " + exc_class. Il punto è che questo tipo di analisi statica non è veramente possibile in un linguaggio dinamico come Python.
Daniel Pryden

7
A proposito, puoi solo find /path/to/library -name '*.py' | grep 'raise 'ottenere risultati simili :)
Andrey Vlasovskikh

23

Dovresti rilevare solo le eccezioni che gestirai.

Cogliere tutte le eccezioni in base ai loro tipi concreti non ha senso. Dovresti catturare eccezioni specifiche che puoi e si gestire. Per altre eccezioni, puoi scrivere un catch generico che cattura "base Exception", lo registra (usa la str()funzione) e termina il tuo programma (o fa qualcos'altro che è appropriato in una situazione di crash).

Se ce la farai davvero tutte le eccezioni e sei sicuro che nessuna di esse sia fatale (ad esempio, se stai eseguendo il codice in una sorta di ambiente sandbox), il tuo approccio per catturare BaseException generica si adatta ai tuoi obiettivi.

Potresti anche essere interessato al riferimento alle eccezioni di lingua , non a un riferimento per la libreria che stai utilizzando.

Se il riferimento alla libreria è davvero scarso e non rigetta le proprie eccezioni quando rileva quelle di sistema, l'unico approccio utile è eseguire i test (magari aggiungerlo alla suite di test, perché se qualcosa non è documentato, potrebbe cambiare!) . Elimina un file cruciale per il tuo codice e controlla quale eccezione viene generata. Fornisci troppi dati e controlla quale errore produce.

Dovrai comunque eseguire i test, poiché, anche se il metodo per ottenere le eccezioni dal codice sorgente esistesse, non ti darebbe alcuna idea di come dovresti gestirle . Forse dovresti visualizzare il messaggio di errore "File needful.txt non trovato!" quando prendi IndexError? Solo il test può dirlo.


27
Certo, ma come si può decidere quali eccezioni gestire se non sa cosa potrebbe essere lanciato ??
GabiMe

@ bugspy.net, ho corretto la mia risposta per riflettere questa questione
P Shved

Forse è il momento per l'analizzatore del codice sorgente che può scoprirlo? Non dovrebbe essere troppo difficile da sviluppare, penso
GabiMe

@ bugspy.net, ho incoraggiato la clausola sul perché potrebbe non essere il momento per farlo.
P Shved

Certo che hai ragione Tuttavia può essere ancora interessante, durante lo sviluppo, sapere quali tipi di eccezioni possono verificarsi.
hek2mgl

11

Lo strumento corretto per risolvere questo problema è unittests. Se stai riscontrando eccezioni sollevate da codice reale che gli unittest non sollevano, allora hai bisogno di più unittest.

Considera questo

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

l'anatra può essere qualsiasi oggetto

Ovviamente puoi avere un AttributeErrorif duck non ha ciarlatano, un TypeErrorif duck ha un ciarlatano ma non è richiamabile. Non hai idea di cosa duck.quack()potrebbe aumentare, forse anche aDuckError o qualcosa del genere

Supponiamo ora di avere un codice come questo

arr[i] = get_something_from_database()

Se solleva un IndexError non sai se proviene da arr [i] o dal profondo della funzione database. di solito non importa tanto dove si è verificata l'eccezione, piuttosto che qualcosa è andato storto e quello che volevi che accadesse non è successo.

Una tecnica utile è catturare e forse rilanciare l'eccezione in questo modo

except Exception as e
    #inspect e, decide what to do
    raise

Perché prenderlo se hai intenzione di "rilanciarlo"?
Tarnay Kálmán

Non devi per rilanciare esso, che è ciò che il commento è stato supposto per indicare.
John La Rooy

2
Puoi anche scegliere di registrare l'eccezione da qualche parte e poi rilanciare
John La Rooy

3
Non credo che scrivere unit test sia la risposta. La domanda è "come posso scoprire quali eccezioni aspettarsi" e scrivere unit test non ti aiuterà a scoprirlo. Infatti, per scrivere lo unit test devi già sapere quali eccezioni aspettarti quindi per scrivere uno unit test corretto devi rispondere anche alla domanda originale.
Bruno Ranschaert

6

Nessuno ha spiegato finora, perché non puoi avere un elenco completo e corretto al 100% di eccezioni, quindi ho pensato che valesse la pena commentare. Uno dei motivi è una funzione di prima classe. Diciamo che hai una funzione come questa:

def apl(f,arg):
   return f(arg)

Ora aplpuò sollevare qualsiasi eccezione che fsolleva. Sebbene non ci siano molte funzioni del genere nella libreria principale, tutto ciò che utilizza la comprensione dell'elenco con filtri personalizzati, mappa, riduzione, ecc. È interessato.

La documentazione e gli analizzatori di sorgenti sono le uniche fonti di informazione "serie" qui. Tieni a mente quello che non possono fare.


5

Mi sono imbattuto in questo quando ho usato socket, volevo scoprire tutte le condizioni di errore in cui mi sarei imbattuto (quindi piuttosto che provare a creare errori e capire quale socket volevo solo un elenco conciso). Alla fine ho finito col grep'ing "/usr/lib64/python2.4/test/test_socket.py" per "raise":

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

Che è un elenco piuttosto conciso di errori. Ovviamente questo funziona solo caso per caso e dipende dall'accuratezza dei test (cosa che di solito lo sono). Altrimenti è necessario prendere praticamente tutte le eccezioni, registrarle e analizzarle e capire come gestirle (cosa che con i test unitari non sarebbe difficile).


4
Questo rafforza la mia tesi, che la gestione delle eccezioni in Python è molto problematica, se abbiamo bisogno di usare grep o analizzatori di sorgenti per gestire qualcosa di così semplice (che ad esempio in java esisteva dal primo giorno. A volte la verbosità è una buona cosa. Java è prolisso ma almeno non ci sono brutte sorprese)
GabiMe

@GabiMe, non è che questa capacità (o la digitazione statica in generale) sia un proiettile d'argento per prevenire tutti i bug. Java è pieno di brutte sorprese. Ecco perché eclipse si blocca regolarmente.
John La Rooy

2

Ci sono due modi in cui ho trovato informativo. Il primo, esegui il codice in iPython, che visualizzerà il tipo di eccezione.

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Nel secondo modo ci accontentiamo di catturare troppo e migliorarlo nel tempo. Includere tryun'espressione nel codice e catturare except Exception as err. Stampa dati sufficienti per sapere quale eccezione è stata generata. Man mano che vengono generate eccezioni, migliorare il codice aggiungendo una exceptclausola più precisa . Quando ritieni di aver individuato tutte le eccezioni rilevanti, rimuovi quella all inclusive. Una buona cosa da fare comunque perché ingoia gli errori di programmazione.

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)

1

normalmente, dovresti rilevare l'eccezione solo attorno a poche righe di codice. Non vorresti mettere l'intera mainfunzione nel filetry except clausola. per ogni poche righe dovresti sempre ora (o essere in grado di controllare facilmente) quale tipo di eccezione potrebbe essere sollevata.

i documenti hanno un elenco esaustivo di eccezioni incorporate . non provare a fare eccezione a quelle eccezioni che non ti aspetti, potrebbero essere gestite / previste nel codice chiamante.

modifica : ciò che potrebbe essere lanciato dipende ovviamente da ciò che stai facendo! accesso a un elemento casuale di una sequenza IndexError:, elemento casuale di un dict:, KeyErrorecc.

Basta provare a eseguire quelle poche righe in IDLE e causare un'eccezione. Ma unittest sarebbe una soluzione migliore, naturalmente.


1
Questo non risponde alla mia semplice domanda. Non chiedo come progettare la mia gestione delle eccezioni, o quando o come catturare. Chiedo come scoprire cosa potrebbe essere lanciato
GabiMe

1
@ bugspy.net: è impossibile fare quello che chiedi, e questa è una soluzione alternativa perfettamente valida.
Daniel Pryden
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.