Der allgemeine Konsens "keine Ausnahmen verwenden!" Kommt meist aus anderen Sprachen und ist auch dort manchmal veraltet.
In C ++ ist das Auslösen einer Ausnahme aufgrund der Stapelabwicklung sehr kostspielig . Jede lokale Variablendeklaration ähnelt einer with
Anweisung in Python, und das Objekt in dieser Variablen kann Destruktoren ausführen. Diese Destruktoren werden ausgeführt, wenn eine Ausnahme ausgelöst wird, aber auch, wenn von einer Funktion zurückgekehrt wird. Dieses „RAII-Idiom“ ist ein integrales Sprachfeature und für das Schreiben von robustem, korrektem Code überaus wichtig. Daher war es ein Kompromiss zwischen RAII und billigen Ausnahmen, den C ++ für RAII entschied.
In früheren Versionen von C ++ wurde viel Code nicht ausnahmesicher geschrieben: Wenn Sie RAII nicht tatsächlich verwenden, können Speicher und andere Ressourcen leicht verloren gehen. Wenn Sie also Ausnahmen auslösen, wird der Code falsch dargestellt. Dies ist nicht mehr sinnvoll, da selbst in der C ++ - Standardbibliothek Ausnahmen verwendet werden: Sie können nicht so tun, als gäbe es keine Ausnahmen. Bei der Kombination von C-Code mit C ++ treten jedoch weiterhin Ausnahmen auf.
In Java ist jeder Ausnahme ein Stack-Trace zugeordnet. Der Stack-Trace ist beim Debuggen von Fehlern sehr hilfreich, aber es ist mühsam, wenn die Ausnahme nie gedruckt wird, z. B. weil sie nur für den Kontrollfluss verwendet wurde.
In diesen Sprachen sind Ausnahmen also „zu teuer“, um als Kontrollfluss verwendet zu werden. In Python ist dies weniger ein Problem und Ausnahmen sind viel billiger. Darüber hinaus leidet die Python-Sprache bereits unter einem gewissen Aufwand, der die Kosten für Ausnahmen im Vergleich zu anderen Kontrollflusskonstrukten unbemerkt lässt: Beispielsweise ist die Überprüfung, ob ein Diktateintrag mit einem expliziten Mitgliedschaftstest vorhanden if key in the_dict: ...
ist, im Allgemeinen genauso schnell wie der bloße Zugriff auf den Eintrag the_dict[key]; ...
und die Überprüfung, ob Sie Holen Sie sich einen KeyError. Einige integrale Sprachfunktionen (z. B. Generatoren) sind in Ausnahmefällen ausgelegt.
Obwohl es keinen technischen Grund gibt, Ausnahmen in Python gezielt zu vermeiden, besteht immer noch die Frage, ob Sie sie anstelle von Rückgabewerten verwenden sollten. Die Probleme auf Designebene mit Ausnahmen sind:
Sie sind überhaupt nicht offensichtlich. Sie können sich eine Funktion nicht leicht ansehen und sehen, welche Ausnahmen sie auslösen kann, sodass Sie nicht immer wissen, was zu fangen ist. Der Rückgabewert ist in der Regel genauer definiert.
Ausnahmen sind nicht-lokale Kontrollabläufe, die Ihren Code komplizieren. Wenn Sie eine Ausnahme auslösen, wissen Sie nicht, wo der Kontrollfluss fortgesetzt wird. Bei Fehlern, die nicht sofort behoben werden können, ist dies wahrscheinlich eine gute Idee. Wenn Sie Ihren Anrufer über eine Bedingung informieren, ist dies völlig unnötig.
Die Python-Kultur neigt im Allgemeinen zu Ausnahmen, aber es ist leicht, über Bord zu gehen. Stellen Sie sich eine list_contains(the_list, item)
Funktion vor, die prüft, ob die Liste ein Element enthält, das diesem Element entspricht. Wenn das Ergebnis über Ausnahmen kommuniziert wird, ist das absolut ärgerlich, weil wir es so nennen müssen:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Ein Bool zurückzugeben wäre viel klarer:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Wenn die Funktion bereits einen Wert zurückgeben soll, ist die Rückgabe eines speziellen Werts für spezielle Bedingungen eher fehleranfällig, da die Benutzer vergessen, diesen Wert zu überprüfen (dies ist wahrscheinlich die Ursache für 1/3 der Probleme in C). Eine Ausnahme ist normalerweise korrekter.
Ein gutes Beispiel ist eine pos = find_string(haystack, needle)
Funktion, die nach dem ersten Auftreten von suchtneedle
Strings im Heuhaufen-String und die Startposition zurückgibt. Aber was ist, wenn die Heuhaufen-Schnur nicht die Nadelschnur enthält?
Die von C und Python imitierte Lösung besteht darin, einen speziellen Wert zurückzugeben. In C ist dies ein Nullzeiger, in Python ist dies -1
. Dies führt zu überraschenden Ergebnissen, wenn die Position als String-Index ohne Prüfung verwendet wird, insbesondere als-1
es sich um einen gültigen Index in Python handelt. In C gibt Ihnen Ihr NULL-Zeiger mindestens einen Segfault.
In PHP wird ein spezieller Wert eines anderen Typs zurückgegeben: der Boolesche Wert FALSE
anstelle einer Ganzzahl. Wie sich herausstellt, ist dies aufgrund der impliziten Konvertierungsregeln der Sprache nicht besser (beachten Sie jedoch, dass in Python auch Boolesche Werte als Ints verwendet werden können!). Funktionen, die keinen konsistenten Typ zurückgeben, werden im Allgemeinen als sehr verwirrend angesehen.
Eine robustere Variante wäre gewesen, eine Ausnahme auszulösen, wenn die Zeichenfolge nicht gefunden werden kann. Dadurch wird sichergestellt, dass es im normalen Kontrollfluss unmöglich ist, den Spezialwert versehentlich anstelle eines normalen Werts zu verwenden:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
Alternativ kann immer ein Typ zurückgegeben werden, der nicht direkt verwendet werden kann, sondern zuerst entpackt werden muss, z. B. ein result-bool-Tupel, bei dem der Boolesche Wert angibt, ob eine Ausnahme aufgetreten ist oder ob das Ergebnis verwendet werden kann. Dann:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Dies zwingt Sie, Probleme sofort zu behandeln, aber es wird sehr schnell ärgerlich. Es verhindert auch, dass Sie leicht verketten können. Jeder Funktionsaufruf benötigt jetzt drei Codezeilen. Golang ist eine Sprache, die denkt, dass dieses Ärgernis die Sicherheit wert ist.
Ausnahmen sind also nicht ganz unproblematisch und können definitiv überbeansprucht werden, insbesondere wenn sie einen „normalen“ Rückgabewert ersetzen. Wenn Sie jedoch spezielle Bedingungen signalisieren (nicht unbedingt nur Fehler), können Sie mithilfe von Ausnahmen APIs entwickeln, die sauber, intuitiv, benutzerfreundlich und schwer zu missbrauchen sind.
collections.defaultdict
odermy_dict.get(key, default)
der Code viel klarer alstry: my_dict[key] except: return default
NEIN! - nicht generell - Ausnahmen gelten mit Ausnahme einer einzelnen Klasse von Code nicht als gute Flusssteuerungspraxis. Die einzige Stelle, an der Ausnahmen als vernünftige oder sogar bessere Möglichkeit zur Signalisierung eines Zustands angesehen werden, sind Generator- oder Iteratoroperationen. Diese Operationen können jeden möglichen Wert als gültiges Ergebnis zurückgeben, sodass ein Mechanismus zum Signalisieren eines Endes erforderlich ist.
Überlegen Sie, ob Sie eine Binärdatei mit einem Stream von einem Byte nach dem anderen lesen möchten - absolut jeder Wert ist ein potenziell gültiges Ergebnis, aber wir müssen immer noch ein Dateiende signalisieren. Wir haben also die Wahl, jedes Mal zwei Werte (den Bytewert und ein gültiges Flag) zurückzugeben oder eine Ausnahme auszulösen, wenn nichts mehr zu tun ist. In beiden Fällen kann der konsumierende Code folgendermaßen aussehen:
Alternative:
Aber dies wurde, seit PEP 343 implementiert und zurückportiert wurde, alles ordentlich in der
with
Anweisung zusammengefasst. Das Obige wird zur sehr pythonischen:In python3 wurde dies:
Ich fordere Sie nachdrücklich auf, PEP 343 zu lesen, das den Hintergrund, die Gründe, Beispiele usw. enthält.
Es ist auch üblich, eine Ausnahme zu verwenden, um das Ende der Verarbeitung anzuzeigen, wenn Generatorfunktionen zum Anzeigen des Endes verwendet werden.
Ich möchte hinzufügen, dass Ihr Suchbeispiel mit ziemlicher Sicherheit rückwärts ist. Solche Funktionen sollten Generatoren sein, die beim ersten Aufruf die erste Übereinstimmung zurückgeben, dann Substituentenaufrufe, die die nächste Übereinstimmung zurückgeben, und eine
NotFound
Ausnahme auslösen, wenn es keine weiteren Übereinstimmungen gibt.quelle
if
besser wäre. Wenn es um das Debuggen und Testen von Codes oder gar das Begründen des Verhaltens Ihres Codes geht, ist es besser, sich nicht auf Ausnahmen zu verlassen - sie sollten die Ausnahme sein. Ich habe im Produktionscode eine Berechnung gesehen, die über einen Throw / Catch und nicht über einen einfachen Return zurückgegeben wurde. Dies bedeutete, dass bei einer Division durch Null ein zufälliger Wert zurückgegeben wurde und nicht ein Absturz auftrat, bei dem der Fehler mehrere Stunden lang auftrat Debuggen.