Warum verursacht die Verwendung von "oder" in einer Ausnahmeklausel keinen SyntaxError? Gibt es eine gültige Verwendung dafür?

11

Bei der Arbeit, stieß ich auf eine exceptKlausel mit einem orBetreiber:

try:
    # Do something.
except IndexError or KeyError:
    # ErrorHandling

Ich weiß, dass die Ausnahmeklassen als Tupel übergeben werden sollten, aber es hat mich gestört, dass es nicht einmal eine verursachen würde SyntaxError.

Also wollte ich zuerst untersuchen, ob es tatsächlich funktioniert. Und das tut es nicht.

>>> def with_or_raise(exc):
...     try:
...         raise exc()
...     except IndexError or KeyError:
...         print('Got ya!')
...

>>> with_or_raise(IndexError)
Got ya!

>>> with_or_raise(KeyError)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in with_or_raise
KeyError

Die zweite Ausnahme wurde also nicht abgefangen, und wenn man sich den Bytecode ansieht, wird klarer, warum:

>>> import dis
>>> dis.dis(with_or_raise)
  2           0 SETUP_EXCEPT            10 (to 12)

  3           2 LOAD_FAST                0 (exc)
              4 CALL_FUNCTION            0
              6 RAISE_VARARGS            1
              8 POP_BLOCK
             10 JUMP_FORWARD            32 (to 44)

  4     >>   12 DUP_TOP
             14 LOAD_GLOBAL              0 (IndexError)
             16 JUMP_IF_TRUE_OR_POP     20
             18 LOAD_GLOBAL              1 (KeyError)
        >>   20 COMPARE_OP              10 (exception match)
             22 POP_JUMP_IF_FALSE       42
             24 POP_TOP
             26 POP_TOP
             28 POP_TOP

  5          30 LOAD_GLOBAL              2 (print)
             32 LOAD_CONST               1 ('Got ya!')
             34 CALL_FUNCTION            1
             36 POP_TOP
             38 POP_EXCEPT
             40 JUMP_FORWARD             2 (to 44)
        >>   42 END_FINALLY
        >>   44 LOAD_CONST               0 (None)
             46 RETURN_VALUE

Wie wir sehen können, lädt Befehl 14 zuerst die IndexErrorKlasse auf den Stapel. Dann prüft es, ob dieser Wert ist True, was auf die Python-Wahrhaftigkeit zurückzuführen ist, und springt schließlich direkt zu Anweisung 20, wo exception matchdies getan wird. Da Befehl 18 übersprungen wurde, KeyErrorwurde er nie auf den Stapel geladen und stimmt daher nicht überein.

Ich habe mit Python 2.7 und 3.6 das gleiche Ergebnis versucht.

Aber warum ist die Syntax dann gültig? Ich stelle mir vor, dass es eines der folgenden ist:

  1. Es ist ein Artefakt aus einer wirklich alten Version von Python.
  2. Es gibt tatsächlich einen gültigen Anwendungsfall für die Verwendung orinnerhalb einer exceptKlausel.
  3. Dies ist lediglich eine Einschränkung des Python-Parsers, der möglicherweise einen Ausdruck nach dem exceptSchlüsselwort akzeptieren muss .

Meine Stimme ist bei 3 (da ich einige Diskussionen über einen neuen Parser für Python gesehen habe), aber ich hoffe, dass jemand diese Hypothese bestätigen kann. Denn wenn es zum Beispiel 2 wäre, würde ich diesen Anwendungsfall wissen wollen!

Außerdem habe ich keine Ahnung, wie ich diese Erkundung fortsetzen würde. Ich stelle mir vor, ich müsste mich in den Quellcode des CPython-Parsers vertiefen, aber ich weiß nicht, wo ich ihn finden kann, und vielleicht gibt es einen einfacheren Weg?

Loïc Teixeira
quelle

Antworten:

7

In except e, ekann jeder gültige Python - Ausdruck sein:

try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ...

[..] Bei einer exceptKlausel mit einem Ausdruck wird dieser Ausdruck ausgewertet, und die Klausel stimmt mit der Ausnahme überein, wenn das resultierende Objekt mit der Ausnahme „kompatibel“ ist. Ein Objekt ist mit einer Ausnahme kompatibel, wenn es sich um die Klasse oder Basisklasse des Ausnahmeobjekts oder um ein Tupel handelt, das ein mit der Ausnahme kompatibles Element enthält.

https://docs.python.org/3/reference/compound_stmts.html#the-try-statement

Der Ausdruck IndexError or KeyErrorergibt den Wert IndexError. Das entspricht also:

except IndexError:
   ...
täuschen
quelle
Vielen Dank für diese schnelle Antwort! Würden wir es als eine Einschränkung des Parsers betrachten (dh nur einen Ausdruck akzeptieren zu können, anstatt einen spezifischeren) oder eine bewusste Wahl, um die Dinge nicht zu sehr einzuschränken?
Loïc Teixeira
1
Es scheint mir, dass ich mich an die Grundprinzipien von Python halte, einfacher einfacher besser zu sein. Warum neue Regeln erfinden, die nur einschränkend sein können, wenn das Akzeptieren eines Ausdrucks volle Freiheit ohne neue Sonderfälle bedeutet?
Täuschung
Es ist eine bewusste Wahl, die es einem ermöglicht, ein Tupel von Ausnahmen zusammenzustellen, um sie dynamisch abzufangen, und einen solchen Wert in einer exceptAnweisung zu verwenden.
user4815162342
Ich denke, der Grund für die Einschränkung der Möglichkeiten wäre, Entwickler daran zu hindern, Code zu schreiben, der nicht das tut, was sie beabsichtigt haben. Schreiben except IndexError or KeyErrorscheint schließlich eine anständige Sache zu sein. Ich stimme Ihnen jedoch zu, dass es gegen einige andere Werte verstößt, die Python zu respektieren versucht.
Loïc Teixeira
Beachten Sie, dass Python auch erlaubt var == 1 or 2, was für das ungeübte Auge auch "wie eine anständige Sache zum Schreiben aussieht".
Eric
-2

Sie sollten ein n-Tupel von Typen anstelle eines logischen Ausdrucks verwenden (der nur das erste nicht falsche Element zurückgibt):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
user2622016
quelle
Wie in der Frage angegeben, weiß ich, dass die Ausnahmeklassen als Tupel übergeben werden sollten, aber ich habe mich gefragt, warum die Verwendung von orPython noch gültig ist.
Loïc Teixeira