Python: Wie kann ich wissen, welche Ausnahmen von einem Methodenaufruf ausgelöst werden könnten?

86

Gibt es eine Möglichkeit zu wissen (zum Zeitpunkt der Codierung), welche Ausnahmen bei der Ausführung von Python-Code zu erwarten sind? In 90% der Fälle erhalte ich die Basis-Ausnahmeklasse, da ich nicht weiß, welcher Ausnahmetyp möglicherweise ausgelöst wird (und ich nicht aufgefordert werde, die Dokumentation zu lesen. Oft kann eine Ausnahme aus der Tiefe weitergegeben werden Mal ist die Dokumentation nicht aktualisiert oder korrekt). Gibt es ein Tool, um dies zu überprüfen? (wie durch Lesen des Python-Codes und der Bibliotheken)?

GabiMe
quelle
2
Beachten Sie, dass Sie in Python <2.6 auch raiseZeichenfolgen und nicht nur BaseExceptionUnterklassen verwenden können. Wenn Sie also Bibliothekscode aufrufen, der außerhalb Ihrer Kontrolle except Exceptionliegt, reicht dies nicht aus, da keine Zeichenfolgenausnahmen abgefangen werden. Wie andere bereits betont haben, bellen Sie hier den falschen Baum an.
Daniel Pryden
Das wusste ich nicht. Ich dachte außer Ausnahme: .. fängt fast alles.
GabiMe
2
except Exceptionfunktioniert gut zum Abfangen von String-Ausnahmen in Python 2.6 und höher.
Jeffrey Harris

Antworten:

22

Ich denke, eine Lösung könnte nur ungenau sein, weil statische Typisierungsregeln fehlen.

Mir ist kein Tool bekannt, das Ausnahmen überprüft, aber Sie könnten ein eigenes Tool entwickeln, das Ihren Anforderungen entspricht (eine gute Chance, ein wenig mit statischer Analyse zu spielen).

Als ersten Versuch könnten Sie eine Funktion schreiben, die einen AST erstellt, alle RaiseKnoten findet und dann versucht, allgemeine Muster für das Auslösen von Ausnahmen herauszufinden (z. B. direktes Aufrufen eines Konstruktors).

Sei xfolgendes Programm:

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

Erstellen Sie den AST mit dem compilerPaket:

tree = compiler.parse(x)

Definieren Sie dann eine RaiseBesucherklasse:

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

Und gehen Sie die AST-Sammelknoten Raiseentlang:

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),
]

Sie können fortfahren, indem Sie Symbole mithilfe von Compilersymboltabellen auflösen, Datenabhängigkeiten analysieren usw. Oder Sie können einfach ableiten, dass CallFunc(Name('IOError'), ...)"definitiv eine Erhöhung bedeuten sollte IOError", was für schnelle praktische Ergebnisse völlig in Ordnung ist :)

Andrey Vlasovskikh
quelle
Danke für diese interessante Antwort. Ich habe jedoch nicht verstanden, warum ich nach etwas anderem als allen Erhöhungsknoten suchen sollte. Warum sollte ich "Symbole mithilfe von Compilersymboltabellen auflösen und Datenabhängigkeiten analysieren"? Ist die einzige Möglichkeit, eine Ausnahme auszulösen, nicht Raise ()?
GabiMe
1
Angesichts des v.nodesobigen Wertes können Sie nicht wirklich sagen, was das Ding ist Name('IOError')oder Name('e'). Sie wissen nicht , was Wert (e) diejenigen , IOErrorund edarauf hinweisen können, wie sie sogenannte freie Variablen sind. Selbst wenn ihr Bindungskontext bekannt wäre (hier kommen Symboltabellen ins Spiel), sollten Sie eine Art Datenabhängigkeitsanalyse durchführen, um ihre genauen Werte abzuleiten (dies sollte in Python schwierig sein).
Andrey Vlasovskikh
Wenn Sie nach einer praktischen halbautomatischen Lösung suchen, ist eine Liste ['IOError(errno.ENOENT, "not found")', 'e'], die dem Benutzer angezeigt wird, in Ordnung. Sie können jedoch keine tatsächlichen Werteklassen von Variablen ableiten, die durch Zeichenfolgen dargestellt werden :) (Entschuldigung für das erneute Posten)
Andrey Vlasovskikh
1
Ja. Diese Methode ist zwar clever, bietet jedoch keine vollständige Abdeckung. Aufgrund der Dynamik von Python ist es durchaus möglich (obwohl offensichtlich eine schlechte Idee), dass Code, den Sie aufrufen, so etwas tut exc_class = raw_input(); exec "raise " + exc_class. Der Punkt ist, dass diese Art der statischen Analyse in einer dynamischen Sprache wie Python nicht wirklich möglich ist.
Daniel Pryden
7
Übrigens können Sie nur find /path/to/library -name '*.py' | grep 'raise 'ähnliche Ergebnisse
erzielen
24

Sie sollten nur Ausnahmen abfangen, die Sie behandeln werden.

Es ist Unsinn, alle Ausnahmen anhand ihrer konkreten Typen zu erfassen. Sie sollten bestimmte Ausnahmen abfangen, die Sie behandeln können und werden . Für andere Ausnahmen können Sie einen generischen Fang schreiben, der " str()Basisausnahme" abfängt , protokolliert ( Funktion verwenden) und Ihr Programm beendet (oder etwas anderes tut, das in einer Absturzsituation angemessen ist).

Wenn Sie wirklich alle Ausnahmen behandeln und sicher sind, dass keine davon schwerwiegend ist (z. B. wenn Sie den Code in einer Sandbox-Umgebung ausführen), entspricht Ihr Ansatz, generische BaseException abzufangen, Ihren Zielen.

Möglicherweise interessieren Sie sich auch für eine Referenz für Sprachausnahmen , nicht für eine Referenz für die von Ihnen verwendete Bibliothek.

Wenn die Bibliotheksreferenz wirklich schlecht ist und beim Abfangen von Systemausnahmen keine eigenen Ausnahmen erneut ausgelöst wird, besteht der einzig nützliche Ansatz darin, Tests auszuführen (möglicherweise zur Testsuite hinzufügen, da sich dies ändern kann, wenn etwas nicht dokumentiert ist!). . Löschen Sie eine für Ihren Code wichtige Datei und überprüfen Sie, welche Ausnahme ausgelöst wird. Geben Sie zu viele Daten an und überprüfen Sie, welchen Fehler sie verursacht.

Sie müssen ohnehin Tests ausführen, da Sie selbst dann keine Ahnung haben, wie Sie mit diesen umgehen sollen , wenn die Methode zum Abrufen der Ausnahmen per Quellcode vorhanden wäre . Möglicherweise sollten Sie die Fehlermeldung "Datei needful.txt wurde nicht gefunden!" Anzeigen. wann fängst IndexErrordu Nur Test kann sagen.

P Shved
quelle
25
Sicher, aber wie kann man entscheiden, mit welchen Ausnahmen er umgehen soll, wenn er nicht weiß, was geworfen werden könnte?
GabiMe
@ bugspy.net, korrigierte meine Antwort, um diese Angelegenheit zu reflektieren
P Shved
Vielleicht ist es Zeit für einen Quellcode-Analysator, der dies herausfinden kann? Sollte nicht zu schwer zu entwickeln sein, denke ich
GabiMe
@ bugspy.net, ich habe die Klausel ermutigt, warum es möglicherweise nicht die Zeit dafür ist.
P Shved
Sicher hast du recht. Es kann jedoch immer noch interessant sein, während der Entwicklung zu wissen, welche Arten von Ausnahmen auftreten können.
hek2mgl
11

Das richtige Werkzeug, um dieses Problem zu lösen, sind Unittests. Wenn Sie Ausnahmen durch echten Code haben, die von den Unittests nicht ausgelöst werden, benötigen Sie weitere Unittests.

Bedenken Sie

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

Ente kann jedes Objekt sein

Natürlich können Sie eine AttributeErrorIf-Ente haben, die keinen Quacksalber hat, eine TypeErrorIf-Ente, die einen Quacksalber hat, aber nicht aufrufbar ist. Sie haben jedoch keine Ahnung, was sich duck.quack()möglicherweise ergibt, vielleicht sogar ein DuckErroroder so

Angenommen, Sie haben einen solchen Code

arr[i] = get_something_from_database()

Wenn es eine IndexErrorauslöst, wissen Sie nicht, ob es von arr [i] oder tief in der Datenbankfunktion stammt. Normalerweise spielt es keine Rolle, wo die Ausnahme aufgetreten ist, sondern dass etwas schief gelaufen ist und das, was Sie wollten, nicht passiert ist.

Eine praktische Technik besteht darin, die Ausnahme wie diese zu fangen und möglicherweise erneut zu erhöhen

except Exception as e
    #inspect e, decide what to do
    raise
John La Rooy
quelle
Warum fangen Sie es überhaupt, wenn Sie es "reraisen" wollen?
Tarnay Kálmán
Sie müssen nicht haben , um es wieder erhöhen, das ist , was der Kommentar sollte anzuzeigen.
John La Rooy
2
Sie können sich auch dafür entscheiden, die Ausnahme irgendwo zu protokollieren und dann erneut zu erhöhen
John La Rooy
2
Ich denke nicht, dass das Schreiben von Unit-Tests die Antwort ist. Die Frage lautet: "Wie kann ich herausfinden, welche Ausnahmen zu erwarten sind?" Das Schreiben von Komponententests hilft Ihnen nicht dabei, dies herauszufinden. Um den Komponententest zu schreiben, müssen Sie bereits wissen, welche Ausnahmen zu erwarten sind. Um einen korrekten Komponententest zu schreiben, müssen Sie auch die ursprüngliche Frage beantworten.
Bruno Ranschaert
6

Bisher hat niemand erklärt, warum es keine vollständige, 100% korrekte Liste von Ausnahmen gibt. Ich dachte, es lohnt sich, einen Kommentar abzugeben. Einer der Gründe ist eine erstklassige Funktion. Angenommen, Sie haben eine Funktion wie diese:

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

Jetzt aplkann jede Ausnahme fausgelöst werden , die ausgelöst wird. Zwar gibt es in der Kernbibliothek nicht viele solcher Funktionen, doch alles, was das Listenverständnis mit benutzerdefinierten Filtern, Zuordnen, Reduzieren usw. verwendet, ist betroffen.

Die Dokumentation und die Quellenanalysatoren sind hier die einzigen "ernsthaften" Informationsquellen. Denken Sie daran, was sie nicht können.

Viraptor
quelle
4

Bei der Verwendung von Socket bin ich darauf gestoßen. Ich wollte alle Fehlerbedingungen herausfinden, unter denen ich laufen würde (anstatt zu versuchen, Fehler zu erstellen und herauszufinden, welchen Socket ich habe, wollte ich nur eine kurze Liste). Letztendlich habe ich "/usr/lib64/python2.4/test/test_socket.py" für "Raise" gesucht:

$ 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,

Welches ist eine ziemlich kurze Liste von Fehlern. Dies funktioniert natürlich nur von Fall zu Fall und hängt davon ab, ob die Tests genau sind (was normalerweise der Fall ist). Andernfalls müssen Sie so ziemlich alle Ausnahmen abfangen, protokollieren und sezieren und herausfinden, wie Sie damit umgehen sollen (was mit Unit-Tests nicht zu schwierig wäre).

Kurt
quelle
4
Dies stärkt mein Argument, dass die Ausnahmebehandlung in Python sehr problematisch ist, wenn wir Grep- oder Quellanalysatoren verwenden müssen, um mit etwas so Grundlegendem umzugehen (das zum Beispiel in Java vom ersten Tag an existierte. Manchmal ist Ausführlichkeit eine gute Sache. Java ist ausführlich aber zumindest gibt es keine bösen Überraschungen)
GabiMe
@GabiMe, es ist nicht so, dass diese Fähigkeit (oder statische Eingabe im Allgemeinen) eine Silberkugel ist, um alle Fehler zu verhindern. Java steckt voller böser Überraschungen. Deshalb stürzt Eclipse regelmäßig ab.
John La Rooy
2

Es gibt zwei Möglichkeiten, die ich informativ fand. Führen Sie als erstes den Code in iPython aus, der den Ausnahmetyp anzeigt.

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

Auf die zweite Weise geben wir uns damit zufrieden, zu viel zu fangen und verbessern es im Laufe der Zeit. Fügen Sie einen tryAusdruck in Ihren Code ein und fangen Sie except Exception as err. Drucken Sie genügend Daten, um zu wissen, welche Ausnahme ausgelöst wurde. Wenn Ausnahmen ausgelöst werden, verbessern Sie Ihren Code, indem Sie eine genauere exceptKlausel hinzufügen . Wenn Sie das Gefühl haben, alle relevanten Ausnahmen erfasst zu haben, entfernen Sie die All-Inclusive-Ausnahme. Eine gute Sache, weil es Programmierfehler verschluckt.

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

Normalerweise müssen Sie Ausnahmen nur um einige Codezeilen herum abfangen. Sie möchten nicht Ihre gesamte mainFunktion in die try exceptKlausel aufnehmen. Für jede einzelne Zeile sollten Sie jetzt immer (oder in der Lage sein, leicht zu überprüfen), welche Art von Ausnahme ausgelöst werden könnte.

Dokumente enthalten eine vollständige Liste der integrierten Ausnahmen . Versuchen Sie nicht, die Ausnahme auszunehmen, die Sie nicht erwarten. Sie werden möglicherweise im aufrufenden Code behandelt / erwartet.

edit : was möglicherweise geworfen wird, hängt natürlich davon ab, was du tust! Zugriff auf ein zufälliges Element einer Sequenz : IndexError, zufälliges Element eines Diktats: KeyErrorusw.

Versuchen Sie einfach, diese wenigen Zeilen in IDLE auszuführen und eine Ausnahme zu verursachen. Aber unittest wäre natürlich eine bessere Lösung.

SilentGhost
quelle
1
Dies beantwortet meine einfache Frage nicht. Ich frage nicht, wie ich meine Ausnahmebehandlung gestalten soll oder wann oder wie ich sie fangen soll. Ich frage, wie man herausfindet, was geworfen werden könnte
GabiMe
1
@ bugspy.net: Es ist unmöglich zu tun, was Sie fragen, und dies ist eine vollkommen gültige Problemumgehung.
Daniel Pryden