Besser etwas ausprobieren und die Ausnahme abfangen oder testen, ob es zuerst möglich ist, eine Ausnahme zu vermeiden?

138

Soll ich testen if, ob etwas gültig ist oder nur try, um es zu tun und die Ausnahme abzufangen?

  • Gibt es eine solide Dokumentation, die besagt, dass ein Weg bevorzugt wird?
  • Ist ein Weg mehr pythonisch ?

Zum Beispiel sollte ich:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

Oder:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Einige Gedanken ...
PEP 20 sagt:

Fehler sollten niemals stillschweigend vergehen.
Sofern nicht ausdrücklich zum Schweigen gebracht.

Sollte die Verwendung von a tryanstelle von a ifals stillschweigender Fehler interpretiert werden? Und wenn ja, bringen Sie es explizit zum Schweigen, indem Sie es auf diese Weise verwenden, sodass es in Ordnung ist?


Ich beziehe mich nicht auf Situationen, in denen Sie Dinge nur auf eine Weise tun können. beispielsweise:

try:
    import foo
except ImportError:
    import baz
chown
quelle

Antworten:

159

Sie sollten lieber try/exceptüber , if/elsewenn , dass die Ergebnisse in

  • Beschleunigungen (zum Beispiel durch Verhinderung zusätzlicher Suchvorgänge)
  • sauberer Code (weniger Zeilen / leichter zu lesen)

Oft gehen diese Hand in Hand.


Beschleunigungen

Wenn Sie versuchen, ein Element in einer langen Liste zu finden, indem Sie:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

Der Versuch, außer, ist die beste Option, wenn der indexwahrscheinlich in der Liste enthalten ist und der IndexError normalerweise nicht ausgelöst wird. Auf diese Weise vermeiden Sie die Notwendigkeit einer zusätzlichen Suche durch if index < len(my_list).

Python empfiehlt die Verwendung von Ausnahmen, bei denen es sich um eine Phrase aus Dive Into Python handelt . Ihr Beispiel behandelt die Ausnahme nicht nur (ordnungsgemäß), sondern lässt sie nicht stillschweigend passieren , sondern tritt auch nur in dem Ausnahmefall auf, in dem der Index nicht gefunden wird (daher das Wort Ausnahme !).


sauberer Code

In der offiziellen Python-Dokumentation wird EAFP erwähnt : Es ist einfacher, um Vergebung als um Erlaubnis zu bitten, und Rob Knight merkt an, dass das Abfangen von Fehlern, anstatt sie zu vermeiden , zu saubererem und leichter lesbarem Code führen kann. Sein Beispiel sagt es so:

Schlimmer (LBYL 'schau bevor du springst') :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Besser (EAFP: Einfacher um Vergebung zu bitten als um Erlaubnis) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None
Remi
quelle
if index in mylisttestet, ob der Index ein Element der Mylist ist, kein möglicher Index. Sie würden if index < len(mylist)stattdessen wollen .
Ychaouche
1
Das ist sinnvoll, aber wo finde ich die Dokumentation, die klar macht, welche möglichen Ausnahmen für int () ausgelöst werden können. docs.python.org/3/library/functions.html#int Ich kann diese Informationen hier nicht finden.
BrutalSimplicity
20

In diesem speziellen Fall sollten Sie etwas ganz anderes verwenden:

x = myDict.get("ABC", "NO_ABC")

Im Allgemeinen jedoch: Wenn Sie erwarten, dass der Test häufig fehlschlägt, verwenden Sie if. Wenn der Test teuer ist, wenn Sie nur den Vorgang versuchen und die Ausnahme abfangen, wenn er fehlschlägt, verwenden Sie try. Wenn keine dieser Bedingungen zutrifft, gehen Sie mit dem, was leichter zu lesen ist.

Dämmerung -inaktiv-
quelle
1
+1 für die Erklärung unter dem Codebeispiel, das genau richtig ist.
ktdrv
4
Ich denke, es ist ziemlich klar, dass er nicht darum gebeten hat, und er hat den Beitrag jetzt bearbeitet, um ihn noch klarer zu machen.
Agf
9

Die Verwendung tryund exceptdirekt anstelle einer ifWache sollte immer erfolgen, wenn die Möglichkeit einer Rennbedingung besteht. Wenn Sie beispielsweise sicherstellen möchten, dass ein Verzeichnis vorhanden ist, gehen Sie nicht wie folgt vor:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Wenn ein anderer Thread oder Prozess das Verzeichnis zwischen isdirund erstellt mkdir, werden Sie beendet. Tun Sie stattdessen Folgendes:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Das wird nur beendet, wenn das 'foo'-Verzeichnis nicht erstellt werden kann.

John Cowan
quelle
7

Wenn es trivial ist, zu überprüfen, ob etwas fehlschlägt, bevor Sie es tun, sollten Sie dies wahrscheinlich bevorzugen. Schließlich braucht das Erstellen von Ausnahmen (einschließlich der zugehörigen Rückverfolgungen) Zeit.

Ausnahmen sollten verwendet werden für:

  1. Dinge, die unerwartet sind, oder ...
  2. Dinge, bei denen Sie mehr als eine Logikstufe überspringen müssen (z. B. wenn a breakSie nicht weit genug bringt) oder ...
  3. Dinge, bei denen Sie nicht genau wissen, was die Ausnahme im Voraus behandeln wird, oder ...
  4. Dinge, bei denen eine frühzeitige Überprüfung auf Fehler teuer ist (im Vergleich zum Versuch, die Operation durchzuführen)

Beachten Sie, dass die eigentliche Antwort häufig "weder" lautet. In Ihrem ersten Beispiel sollten Sie beispielsweise nur .get()einen Standard angeben:

x = myDict.get('ABC', 'NO_ABC')
Bernstein
quelle
außer das if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'ist eigentlich oft schneller als zu benutzen get, leider. Nicht zu sagen, dass dies das wichtigste Kriterium ist, aber es ist etwas, das man beachten muss.
Agf
@agf: Besser klaren und prägnanten Code schreiben. Wenn etwas später optimiert werden muss, ist es einfach, zurück zu kommen und es neu zu schreiben, aber c2.com/cgi/wiki?PrematureOptimization
Amber
Ich weiß das; Mein Punkt war das if / elseund try / exceptkann seinen Platz haben, auch wenn es fallspezifische Alternativen gibt, weil sie unterschiedliche Leistungsmerkmale haben.
Agf
@agf, wissen Sie, ob die get () -Methode in zukünftigen Versionen so verbessert wird, dass sie (mindestens) so schnell ist wie das explizite Nachschlagen? Übrigens, wenn Sie zweimal nachschlagen (wie in 'ABC' in d: d ['ABC']), ist try: d ['ABC'] außer KeyError: ... nicht am schnellsten?
Remi
2
@Remi Der langsame Teil von .get()ist die Attribut-Suche und der Overhead für Funktionsaufrufe auf Python-Ebene. Die Verwendung von Schlüsselwörtern für integrierte Funktionen geht im Grunde direkt zu C. Ich glaube nicht, dass es bald zu viel schneller wird. Was ifvs. betriffttry , gibt die read dict.get () -Methode einen Zeiger zurück, der einige Leistungsinformationen enthält. Das Verhältnis von Treffern zu Fehlschlägen spielt eine Rolle ( trykann schneller sein, wenn der Schlüssel fast immer vorhanden ist), ebenso wie die Größe des Wörterbuchs.
Agf
5

Wie in den anderen Beiträgen erwähnt, hängt es von der Situation ab. Die Verwendung von try / birgt einige Gefahren, außer dass die Gültigkeit Ihrer Daten im Voraus überprüft wird, insbesondere wenn Sie sie in größeren Projekten verwenden.

  • Der Code im try-Block kann möglicherweise alle Arten von Chaos anrichten, bevor die Ausnahme abgefangen wird. Wenn Sie dies zuvor proaktiv mit einer if-Anweisung überprüfen, können Sie dies vermeiden.
  • Wenn der in Ihrem try-Block aufgerufene Code einen allgemeinen Ausnahmetyp wie TypeError oder ValueError auslöst, können Sie möglicherweise nicht dieselbe Ausnahme abfangen, die Sie erwartet hatten - es kann sich um etwas anderes handeln, das dieselbe Ausnahmeklasse vor oder nach dem Aufrufen auslöst die Zeile, in der Ihre Ausnahme ausgelöst werden kann.

Angenommen, Sie hatten:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

Der IndexError sagt nichts darüber aus, ob er beim Abrufen eines Elements von index_list oder my_list aufgetreten ist.

Peter
quelle
4

Sollte die Verwendung eines Versuchs anstelle eines if als stillschweigender Fehler interpretiert werden? Und wenn ja, bringen Sie es explizit zum Schweigen, indem Sie es auf diese Weise verwenden, sodass es in Ordnung ist?

Die Verwendung trybestätigt, dass ein Fehler auftreten kann, was das Gegenteil davon ist, dass er stillschweigend passiert. Die Verwendung exceptbewirkt, dass es überhaupt nicht bestanden wird.

Die Verwendung try: except:wird in Fällen bevorzugt, in denen die if: else:Logik komplizierter ist. Einfach ist besser als komplex; komplex ist besser als kompliziert; und es ist einfacher, um Vergebung zu bitten als um Erlaubnis.

Was "Fehler niemals stillschweigend passieren sollten" warnt, ist der Fall, in dem Code eine Ausnahme auslösen könnte, über die Sie Bescheid wissen, und in der Ihr Design die Möglichkeit zulässt, Sie aber nicht so entworfen haben, dass Sie mit der Ausnahme umgehen können. Ein Fehler explizit zum Schweigen zu bringen, würde meiner Ansicht nach so etwas wie passin einem exceptBlock tun , was nur mit dem Verständnis geschehen sollte, dass "nichts tun" wirklich die richtige Fehlerbehandlung in der jeweiligen Situation ist. (Dies ist eines der wenigen Male, bei denen ich der Meinung bin, dass ein Kommentar in gut geschriebenem Code wahrscheinlich wirklich benötigt wird.)

In Ihrem speziellen Beispiel ist jedoch keines von beiden angemessen:

x = myDict.get('ABC', 'NO_ABC')

Der Grund, warum alle darauf hinweisen - obwohl Sie Ihren Wunsch nach allgemeinem Verständnis und die Unfähigkeit, ein besseres Beispiel zu finden, anerkennen -, ist, dass es in vielen Fällen tatsächlich gleichwertige Nebenschritte gibt, und die Suche nach ihnen ist der erster Schritt zur Lösung des Problems.

Karl Knechtel
quelle
3

try/exceptFragen Sie sich, wann immer Sie den Kontrollfluss verwenden:

  1. Ist es leicht zu erkennen, wann der tryBlock erfolgreich ist und wann er fehlschlägt?
  2. Kennen Sie alle Nebenwirkungen im tryBlock?
  3. Kennen Sie alle Fälle, in denen der tryBlock die Ausnahme auslöst?
  4. Wenn sich die Implementierung des tryBlocks ändert, verhält sich Ihr Kontrollfluss dann weiterhin wie erwartet?

Wenn die Antwort auf eine oder mehrere dieser Fragen "Nein" lautet, muss möglicherweise viel um Vergebung gebeten werden. höchstwahrscheinlich von deinem zukünftigen Selbst.


Ein Beispiel. Ich habe kürzlich Code in einem größeren Projekt gesehen, das so aussah:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

Im Gespräch mit dem Programmierer stellte sich heraus, dass der beabsichtigte Kontrollfluss war:

Wenn x eine ganze Zahl ist, mache y = foo (x).

Wenn x eine Liste von ganzen Zahlen ist, tun Sie y = bar (x).

Dies funktionierte, weil fooeine Datenbankabfrage durchgeführt wurde und die Abfrage erfolgreich war, wenn xes sich um eine Ganzzahl handelte, und eine ProgrammingErrorif- xListe ausgelöst wurde.

Verwenden try/exceptist hier eine schlechte Wahl:

  1. Der Name der Ausnahme ProgrammingErrorverrät nicht das eigentliche Problem (das xist keine Ganzzahl), was es schwierig macht zu sehen, was los ist.
  2. Das ProgrammingErrorwird während eines Datenbankaufrufs ausgelöst, was Zeit verschwendet. Es würde wirklich schrecklich werden, wenn sich herausstellen würde, dass fooetwas in die Datenbank geschrieben wird, bevor eine Ausnahme ausgelöst wird, oder der Status eines anderen Systems geändert wird.
  3. Es ist unklar, ob ProgrammingErrornur xeine Liste von ganzen Zahlen ausgelöst wird. Angenommen foo, die Datenbankabfrage enthält einen Tippfehler . Dies könnte auch a auslösen ProgrammingError. Die Folge ist, dass bar(x)jetzt auch aufgerufen wird, wenn xes sich um eine Ganzzahl handelt. Dies kann zu kryptischen Ausnahmen führen oder zu unvorhersehbaren Ergebnissen führen.
  4. Der try/exceptBlock fügt allen zukünftigen Implementierungen von eine Anforderung hinzu foo. Wann immer wir uns ändern foo, müssen wir jetzt darüber nachdenken, wie es mit Listen umgeht, und sicherstellen, dass es einen ProgrammingErrorund beispielsweise keinen AttributeErroroder gar keinen Fehler auslöst.
Elias Strehle
quelle
0

Für eine allgemeine Bedeutung können Sie Redewendungen und Anti-Redewendungen in Python: Ausnahmen lesen .

In Ihrem speziellen Fall sollten Sie, wie andere angegeben haben, Folgendes verwenden dict.get():

get (Schlüssel [, Standard])

Geben Sie den Wert für key zurück, wenn sich key im Wörterbuch befindet, andernfalls standardmäßig. Wenn kein Standardwert angegeben ist, wird standardmäßig None verwendet, sodass diese Methode niemals einen KeyError auslöst.

etuardu
quelle
Ich glaube nicht, dass der Link diese Situation überhaupt abdeckt. In den Beispielen geht es um die Behandlung von Dingen , die echte Fehler darstellen , und nicht darum, ob die Ausnahmebehandlung für erwartete Situationen verwendet werden soll.
Agf
Erster Link ist veraltet.
Timofei Bondarev