Die Einstellung
Ich habe oft Probleme festzustellen, wann und wie ich Ausnahmen verwenden soll. Betrachten wir ein einfaches Beispiel: Angenommen, ich schabe eine Webseite mit der Aufschrift " http://www.abevigoda.com/ ", um festzustellen, ob Abe Vigoda noch am Leben ist. Dazu müssen wir nur die Seite herunterladen und nach Zeiten suchen, in denen der Ausdruck "Abe Vigoda" erscheint. Wir geben den ersten Auftritt zurück, da dies Abes Status einschließt. Konzeptionell sieht es so aus:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Wobei parse_abe_status(s)
eine Zeichenkette der Form "Abe Vigoda ist etwas " annimmt und den Teil " etwas " zurückgibt .
Bevor Sie argumentieren, dass es viel bessere und robustere Möglichkeiten gibt, diese Seite nach Abes Status zu durchsuchen, denken Sie daran, dass dies nur ein einfaches und ausgeklügeltes Beispiel ist, das verwendet wird, um eine häufige Situation hervorzuheben, in der ich mich befinde.
Wo kann dieser Code auf Probleme stoßen? Einige "erwartete" Fehler sind unter anderem:
download_page
ist möglicherweise nicht in der Lage, die Seite herunterzuladen, und wirft eineIOError
.- Die URL verweist möglicherweise nicht auf die richtige Seite, oder die Seite wurde falsch heruntergeladen, sodass es keine Treffer gibt.
hits
ist also die leere Liste. - Die Webseite wurde geändert, wodurch möglicherweise unsere Vermutungen über die Seite falsch sind. Vielleicht erwarten wir 4 Erwähnungen von Abe Vigoda, aber jetzt finden wir 5.
- Aus bestimmten Gründen ist
hits[0]
möglicherweise keine Zeichenfolge der Form "Abe Vigoda ist etwas " vorhanden und kann daher nicht korrekt analysiert werden.
Der erste Fall ist für mich eigentlich kein Problem: Ein IOError
wird geworfen und kann vom Aufrufer meiner Funktion bearbeitet werden. Betrachten wir also die anderen Fälle und wie ich damit umgehen könnte. parse_abe_status
Nehmen wir aber zunächst an, wir implementieren auf die dümmste Art und Weise:
def parse_abe_status(s):
return s[13:]
Es wird nämlich keine Fehlerprüfung durchgeführt. Nun zu den Optionen:
Option 1: Rückkehr None
Ich kann dem Anrufer sagen, dass etwas schief gelaufen ist, indem ich zurückkehre None
:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
if not hits:
return None
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Wenn der Anrufer None
von meiner Funktion erhält , sollte er davon ausgehen, dass Abe Vigoda nicht erwähnt wurde, sodass ein Fehler aufgetreten ist. Aber das ist ziemlich vage, oder? Und es hilft nichts, wenn hits[0]
es nicht so ist, wie wir es uns vorgestellt haben.
Auf der anderen Seite können wir einige Ausnahmen machen:
Option 2: Verwenden von Ausnahmen
Wenn hits
leer, IndexError
wird beim Versuch ein geworfen hits[0]
. Es ist jedoch nicht zu erwarten, dass der Anrufer eine IndexError
von meiner Funktion ausgelöste Aufgabe übernimmt, da er keine Ahnung hat, woher diese IndexError
stammt. es hätte vorbei geworfen werden können find_all_mentions
, soweit er weiß. Deshalb erstellen wir eine benutzerdefinierte Ausnahmeklasse, um dies zu handhaben:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Was ist nun, wenn sich die Seite geändert hat und unerwartet viele Treffer zu verzeichnen sind? Dies ist keine Katastrophe, da der Code möglicherweise immer noch funktioniert, ein Anrufer jedoch besonders vorsichtig sein oder eine Warnung protokollieren möchte. Also werfe ich eine Warnung:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Schließlich stellen wir möglicherweise fest, dass status
das weder lebend noch tot ist. Vielleicht stellte sich aus irgendeinem Grund heraus, dass es heute so war comatose
. Dann möchte ich nicht zurückkehren False
, da dies impliziert, dass Abe tot ist. Was soll ich hier machen? Wirf wahrscheinlich eine Ausnahme. Aber welche? Soll ich eine benutzerdefinierte Ausnahmeklasse erstellen?
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
if status not in ['alive', 'dead']:
raise SomeTypeOfError("Status is an unexpected value.")
# he's either alive or dead
return status == "alive"
Option 3: Irgendwo dazwischen
Ich denke, dass die zweite Methode mit Ausnahmen vorzuziehen ist, aber ich bin nicht sicher, ob ich Ausnahmen richtig darin verwende. Ich bin gespannt, wie erfahrene Programmierer damit umgehen würden.
quelle
Die akzeptierte Antwort verdient es, akzeptiert zu werden und beantwortet die Frage. Ich schreibe dies nur, um ein bisschen zusätzlichen Hintergrund zu bieten.
Ein Credo von Python ist: Es ist einfacher, um Verzeihung zu bitten als um Erlaubnis. Dies bedeutet, dass Sie normalerweise nur Dinge tun und, wenn Sie Ausnahmen erwarten, diese behandeln. Im Gegensatz zu früheren Überprüfungen, um sicherzustellen, dass Sie keine Ausnahme erhalten.
Ich möchte Ihnen anhand eines Beispiels zeigen, wie dramatisch der Unterschied in der Mentalität von C ++ / Java ist. Eine for-Schleife in C ++ sieht normalerweise so aus:
Eine Möglichkeit, darüber nachzudenken:
myvector[k]
Wenn Sie auf where k> = myvector.size () zugreifen, wird eine Ausnahme ausgelöst. Man könnte das also im Prinzip (sehr umständlich) als Versuchsfang schreiben.Oder etwas ähnliches. Überlegen Sie nun, was in einer Python-for-Schleife passiert:
Wie funktioniert das Die for-Schleife nimmt das Ergebnis von range (1) und ruft iter () auf, um einen Iterator dorthin zu holen.
Dann ruft es bei jeder Schleifeniteration das nächste Mal auf, bis ...:
Mit anderen Worten, eine for-Schleife in Python ist eigentlich ein Versuch, außer in der Verkleidung.
Was die konkrete Frage betrifft, denken Sie daran, dass Ausnahmen die normale Funktionsausführung stoppen und separat behandelt werden müssen. In Python sollten Sie sie frei werfen, wenn es keinen Sinn macht, den Rest des Codes in Ihrer Funktion auszuführen, und / oder wenn keine der Rückgaben korrekt widerspiegelt, was in der Funktion passiert ist. Beachten Sie, dass eine frühzeitige Rückkehr von einer Funktion anders ist: Eine frühzeitige Rückkehr bedeutet, dass Sie die Antwort bereits herausgefunden haben und den Rest des Codes nicht benötigen, um die Antwort herauszufinden. Ich sage, dass Ausnahmen ausgelöst werden sollten, wenn die Antwort nicht bekannt ist und der Rest des Codes zur Bestimmung der Antwort nicht vernünftig ausgeführt werden kann. Jetzt ist es nur noch eine Frage der Dokumentation, sich selbst "richtig zu reflektieren", wie die Ausnahmen, die Sie auslösen.
Im Falle Ihres speziellen Codes würde ich sagen, dass jede Situation, die dazu führt, dass Treffer eine leere Liste sind, ausgelöst werden sollte. Warum? Nun, so wie Ihre Funktion eingerichtet ist, gibt es keine Möglichkeit, die Antwort zu bestimmen, ohne Treffer zu analysieren. Wenn also Treffer nicht syntaktisch analysiert werden können, entweder weil die URL schlecht ist oder weil Treffer leer sind, kann die Funktion die Frage nicht beantworten und es auch nicht wirklich versuchen.
In diesem speziellen Fall würde ich argumentieren, dass Sie auch dann noch werfen sollten, wenn es Ihnen gelingt, zu analysieren und keine vernünftige Antwort zu erhalten (lebend oder tot). Warum? Weil die Funktion einen Booleschen Wert zurückgibt. Die Rücksendung von None ist für Ihren Kunden sehr gefährlich. Wenn sie das Kontrollkästchen Keine aktivieren, tritt kein Fehler auf, sondern es wird nur stillschweigend als Falsch behandelt. Ihr Client muss also grundsätzlich immer eine Prüfung durchführen, wenn keine vorhanden ist, wenn er keine stillen Fehler wünscht. Sie sollten also wahrscheinlich nur werfen.
quelle
Sie sollten Ausnahmen verwenden, wenn etwas Außergewöhnliches auftritt. Das heißt, etwas, das bei ordnungsgemäßer Verwendung der Anwendung nicht auftreten sollte. Wenn es für den Verbraucher Ihrer Methode zulässig und zu erwarten ist, nach etwas zu suchen, das nicht gefunden wird, ist "nicht gefunden" kein Ausnahmefall. In diesem Fall sollten Sie null oder "None" oder {} oder etwas zurückgeben, das auf eine leere Rückgabemenge hinweist.
Wenn Sie andererseits wirklich erwarten, dass die Konsumenten Ihrer Methode immer das finden, wonach gesucht wird (es sei denn, sie haben es irgendwie vermasselt), dann ist es eine Ausnahme, dies nicht zu finden, und Sie sollten dies tun.
Der Schlüssel ist, dass die Ausnahmebehandlung teuer sein kann - Ausnahmen sollen Informationen über den Zustand Ihrer Anwendung sammeln, wenn sie auftreten, z. Ich glaube nicht, dass Sie das versuchen.
quelle
String
und Sie "None" als Indikator auswählen, bedeutet dies, dass Sie darauf achten müssen, dass "None" niemals ein gültiger Wert ist. Beachten Sie außerdem, dass es einen Unterschied gibt, ob Sie die Daten anzeigen und keinen Wert finden und die Daten nicht abrufen können. Daher können wir die Daten nicht finden. Das gleiche Ergebnis für diese beiden Fälle zu haben, bedeutet, dass Sie keine Sichtbarkeit haben, wenn Sie keinen Wert erhalten, wenn Sie erwarten, dass es einen gibt.Wenn ich eine Funktion schreibe
Ich würde es an
return True
oderFalse
in den Fällen schreiben, in denen ich mir der einen oder anderen absolut sicher bin, undraise
in jedem anderen Fall einen Fehler (zBraise ValueError("Status neither 'dead' nor 'alive'")
). Dies liegt daran, dass die Funktion, die mine aufruft, einen Booleschen Wert erwartet, und wenn ich dies nicht mit Sicherheit angeben kann, sollte der reguläre Programmfluss nicht fortgesetzt werden.So etwas wie Ihr Beispiel, bei dem Sie eine andere Anzahl von "Treffern" erhalten als erwartet, würde ich wahrscheinlich ignorieren. Solange einer der Treffer noch zu meinem Muster "Abe Vigoda ist {dead | alive}" passt, ist das in Ordnung. Auf diese Weise kann die Seite neu angeordnet werden, erhält jedoch weiterhin die entsprechenden Informationen.
Eher, als
Ich würde ausdrücklich prüfen:
da dies tendenziell "billiger" ist als das aufstellen der
try
.Ich stimme dir zu
IOError
; Ich würde auch nicht versuchen, Fehler beim Herstellen einer Verbindung mit der Website zu machen. Wenn dies aus irgendeinem Grund nicht möglich ist, ist dies nicht der geeignete Ort, um die Verbindung herzustellen (da dies uns nicht bei der Beantwortung unserer Frage hilft) auf die aufrufende Funktion.quelle