Verwenden Sie try vs if in Python


145

Gibt es eine Begründung, um zu entscheiden, welches tryoder welche ifKonstrukte verwendet werden sollen, wenn eine Variable auf einen Wert getestet wird?

Beispielsweise gibt es eine Funktion, die entweder eine Liste oder keinen Wert zurückgibt. Ich möchte das Ergebnis vor der Verarbeitung überprüfen. Welche der folgenden Möglichkeiten wäre vorzuziehen und warum?

result = function();
if (result):
    for r in result:
        #process items

oder

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

Verwandte Diskussion:

Überprüfen der Existenz von Mitgliedern in Python


Denken Sie daran, dass Sie, wenn Ihre Zeilengruppe #process items einen TypeError auslösen kann, einen weiteren Versuch einschließen müssen: außer: block. Für dieses spezielle Beispiel würde ich einfach ein if verwenden:
richo

Antworten:


237

Sie hören oft, dass Python den EAFP- Stil ("es ist einfacher, um Vergebung als um Erlaubnis zu bitten") gegenüber dem LBYL- Stil ("schauen, bevor Sie springen") fördert . Für mich ist es eine Frage der Effizienz und Lesbarkeit.

In Ihrem Beispiel (sagen wir, anstatt eine Liste oder eine leere Zeichenfolge zurückzugeben, sollte die Funktion eine Liste zurückgeben oder None) würde resultich den try/exceptAnsatz verwenden , wenn Sie erwarten, dass 99% der Zeit tatsächlich etwas Iterierbares enthalten . Es wird schneller sein, wenn Ausnahmen wirklich außergewöhnlich sind. Wenn resultist Nonemehr als 50% der Zeit, dann verwenden ifist wahrscheinlich besser.

Um dies mit ein paar Messungen zu unterstützen:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Während eine ifAnweisung Sie immer kostet, ist es fast kostenlos, einen try/exceptBlock einzurichten . Aber wenn Exceptiontatsächlich ein auftritt, sind die Kosten viel höher.

Moral:

  • Es ist vollkommen in Ordnung (und "pythonisch"), es try/exceptfür die Flusskontrolle zu verwenden.
  • aber es macht am meisten Sinn, wenn Exceptions tatsächlich außergewöhnlich sind.

Aus den Python-Dokumenten:

EAFP

Es ist einfacher, um Vergebung zu bitten als um Erlaubnis. Dieser übliche Python-Codierungsstil setzt die Existenz gültiger Schlüssel oder Attribute voraus und fängt Ausnahmen ab, wenn sich die Annahme als falsch herausstellt. Dieser klare und schnelle Stil zeichnet sich durch das Vorhandensein vieler tryund exceptAussagen aus. Die Technik steht im Gegensatz zum LBYL- Stil, der vielen anderen Sprachen wie C gemeinsam ist.


1
Danke dir. Ich sehe, dass in diesem Fall die Erwartung des Ergebnisses die Begründung sein kann.
Artdanil

6
.... und das ist einer (von vielen) Gründen, warum es so schwierig ist, eine wirklich optimierende JIT für Python durchzuführen. Da die vor kurzem in der Beta luajit 2 beweist, dynamische Sprachen können wirklich sein, wirklich schnell; Dies hängt jedoch stark vom ursprünglichen Sprachdesign und dem Stil ab, den es fördert. (In einem ähnlichen Zusammenhang ist das Sprachdesign der Grund, warum selbst die besten JavaScirpt-JITs nicht mit LuaJIT 1 verglichen werden können, geschweige denn 2)
Javier

1
@ 2rs2ts: Ich habe gerade selbst ähnliche Timings gemacht. In Python 3 try/exceptwar es 25% schneller als if key in d:in Fällen, in denen sich der Schlüssel im Wörterbuch befand. Es war viel langsamer, wenn der Schlüssel nicht wie erwartet im Wörterbuch enthalten war, und stimmte mit dieser Antwort überein.
Tim Pietzcker

5
Ich weiß jedoch, dass dies eine alte Antwort ist: Die Verwendung von Aussagen wie 1/1mit timeit ist keine gute Wahl, da sie optimiert werden (siehe dis.dis('1/1')und beachten Sie, dass keine Unterteilung erfolgt).
Andrea Corbellini

1
Dies hängt auch von der Operation ab, die Sie ausführen möchten. Eine einzelne Division ist schnell, das Abfangen eines Fehlers ist etwas teuer. Aber auch in Fällen, in denen die Kosten für die Ausnahmebehandlung akzeptabel sind, sollten Sie sorgfältig überlegen, was Sie vom Computer verlangen. Wenn die Operation selbst unabhängig von ihren Ergebnissen sehr teuer ist und es eine kostengünstige Möglichkeit gibt, festzustellen, ob Erfolg möglich ist: Verwenden Sie sie. Beispiel: Kopieren Sie nicht die Hälfte einer Datei, nur um festzustellen, dass nicht genügend Speicherplatz vorhanden ist.
Bachsau

14

Ihre Funktion sollte keine gemischten Typen zurückgeben (dh Liste oder leere Zeichenfolge). Es sollte eine Liste von Werten oder nur eine leere Liste zurückgeben. Dann müssten Sie nicht auf irgendetwas testen, dh Ihr Code kollabiert zu:

for r in function():
    # process items

2
In diesem Punkt stimme ich Ihnen voll und ganz zu. Die Funktion ist jedoch nicht meine und ich benutze sie nur.
Artdanil

2
@artdanil: Sie könnten diese Funktion also in eine Funktion einschließen, die Sie schreiben und die so funktioniert, wie die, an die Brandon Corfman denkt.
Quamrana

24
was die Frage aufwirft: Sollte der Wrapper ein if oder einen Versuch verwenden, um den nicht iterierbaren Fall zu behandeln?
JCDyer

@quamrana das ist genau das, was ich versuche zu tun. Wie @jcd betonte, ist die Frage, wie ich mit der Situation in der Wrapper-Funktion umgehen soll.
Artdanil

12

Bitte ignorieren Sie meine Lösung, wenn der von mir bereitgestellte Code auf den ersten Blick nicht offensichtlich ist und Sie die Erklärung nach dem Codebeispiel lesen müssen.

Kann ich davon ausgehen, dass "kein Wert zurückgegeben" bedeutet, dass der Rückgabewert "Keine" ist? Wenn ja oder wenn der "Nein-Wert" boolesch falsch ist, können Sie Folgendes tun, da Ihr Code "Kein Wert" im Wesentlichen als "Nicht iterieren" behandelt:

for r in function() or ():
    # process items

Wenn function()etwas zurückgegeben wird, das nicht wahr ist, iterieren Sie über das leere Tupel, dh Sie führen keine Iterationen aus. Dies ist im Wesentlichen LBYL.


4

Ihr zweites Beispiel ist fehlerhaft - der Code löst niemals eine TypeError-Ausnahme aus, da Sie sowohl Zeichenfolgen als auch Listen durchlaufen können. Das Durchlaufen einer leeren Zeichenfolge oder Liste ist ebenfalls gültig - es wird der Hauptteil der Schleife nullmal ausgeführt.


4

Welche der folgenden Möglichkeiten wäre vorzuziehen und warum?

Look Before You Leap ist in diesem Fall vorzuziehen. Mit dem Ausnahmeansatz kann ein TypeError an einer beliebigen Stelle in Ihrem Schleifenkörper auftreten und abgefangen und weggeworfen werden. Dies ist nicht das, was Sie möchten, und macht das Debuggen schwierig.

(Ich stimme jedoch Brandon Corfman zu: Die Rückgabe von None für 'no items' anstelle einer leeren Liste ist fehlerhaft. Es ist eine unangenehme Angewohnheit von Java-Codierern, die in Python nicht zu sehen ist. Oder Java.)


4

Im Allgemeinen habe ich den Eindruck, dass Ausnahmen außergewöhnlichen Umständen vorbehalten sein sollten. Wenn resulterwartet wird, dass die Festplatte niemals leer ist (dies kann jedoch der Fall sein, wenn beispielsweise eine Festplatte abstürzt usw.), ist der zweite Ansatz sinnvoll. Wenn andererseits ein resultLeerzeichen unter normalen Bedingungen durchaus sinnvoll ist , ist es sinnvoller, es mit einer ifAussage zu testen .

Ich dachte an das (häufigere) Szenario:

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

anstelle des Äquivalents:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

Dies ist ein Beispiel für den Unterschied zwischen den Ansätzen, die Tim Pietzcker erwähnt: Der erste ist LBYL, der zweite ist EAFP
Managu

++Verwenden Sie += 1stattdessen nur eine kurze Notiz, die in Python nicht funktioniert .
tgray

3

bobince weist weise darauf hin, dass das Umschließen des zweiten Falls auch TypeErrors in der Schleife abfangen kann, was nicht das ist, was Sie wollen. Wenn Sie wirklich einen Versuch verwenden möchten, können Sie vor der Schleife testen, ob er iterierbar ist

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

Wie Sie sehen können, ist es ziemlich hässlich. Ich schlage es nicht vor, aber es sollte der Vollständigkeit halber erwähnt werden.


In meinen Augen ist es immer ein schlechter Codierungsstil, absichtlich auf Tippfehler zu stoßen. Ein Entwickler sollte wissen, was ihn erwartet, und bereit sein, mit diesen Werten ausnahmslos umzugehen. Wann immer eine Operation das tat, was sie tun sollte (aus Sicht des Programmierers) und die erwarteten Ergebnisse eine Liste sind, oder Noneüberprüfen Sie das Ergebnis immer mit is Noneoder is not None. Auf der anderen Seite ist es auch ein schlechter Stil, Ausnahmen für legitime Ergebnisse zu erheben. Ausnahmen sind für die unerwarteten Dinge. Beispiel: str.find()Gibt -1 zurück, wenn nichts gefunden wird, da die Suche selbst fehlerfrei abgeschlossen wurde.
Bachsau

1

In Bezug auf die Leistung ist die Verwendung des try-Blocks für Code, der normalerweise keine Ausnahmen auslöst, schneller als die Verwendung der if-Anweisung jedes Mal. Die Entscheidung hängt also von der Wahrscheinlichkeit von Ausnahmefällen ab.


-5

Als allgemeine Faustregel sollten Sie niemals try / catch oder andere Ausnahmebehandlungsmittel verwenden, um den Fluss zu steuern. Auch wenn die Iteration hinter den Kulissen über das Auslösen von StopIterationAusnahmen gesteuert wird , sollten Sie Ihr erstes Code-Snippet dem zweiten vorziehen.


7
Dies gilt für Java. Python begrüßt EAFP ziemlich stark.
Fengb

3
Es ist immer noch eine seltsame Praxis. Meiner Erfahrung nach sind die Gehirne der meisten Programmierer so verdrahtet, dass sie EAFP überspringen. Am Ende fragen sie sich, warum ein bestimmter Pfad ausgewählt wurde. Davon abgesehen gibt es eine Zeit und einen Ort, um jede Regel zu brechen.
Ilowe
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.