Pourquoi l'utilisation de `ou` dans une clause except ne provoque-t-elle pas une erreur de syntaxe? Y a-t-il une utilisation valable pour cela?

11

Au travail, je suis tombé sur une exceptclause avec un oropérateur:

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

Je sais que les classes d'exception doivent être passés comme un tuple, mais il me Bugged qu'il ne serait même pas causer SyntaxError.

J'ai donc d'abord voulu vérifier si cela fonctionnait réellement. Et ce n'est pas le cas.

>>> 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

Donc, il n'a pas intercepté la deuxième exception, et en regardant le bytecode, il devient plus clair pourquoi:

>>> 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

On voit donc que l'instruction 14 charge d'abord la IndexErrorclasse sur la pile. Ensuite, il vérifie si cette valeur est True, ce qui est dû à la véracité de Python et saute finalement directement à l'instruction 20 où exception matchcela est fait. Étant donné que l'instruction 18 a été ignorée, KeyErrorn'a jamais été chargée dans la pile et ne correspond donc pas.

J'ai essayé avec Python 2.7 et 3.6, même résultat.

Mais alors, pourquoi est-ce une syntaxe valide? J'imagine que c'est l'un des suivants:

  1. C'est un artefact d'une version très ancienne de Python.
  2. Il existe en fait un cas d'utilisation valide à utiliser ordans une exceptclause.
  3. C'est simplement une limitation de l'analyseur Python qui pourrait devoir accepter n'importe quelle expression après le exceptmot - clé.

Mon vote est sur 3 (étant donné que j'ai vu une discussion sur un nouvel analyseur pour Python) mais j'espère que quelqu'un pourra confirmer cette hypothèse. Parce que si c'était 2 par exemple, je veux connaître ce cas d'utilisation!

De plus, je ne sais pas du tout comment je continuerais cette exploration. J'imagine que je devrais creuser dans le code source de l'analyseur CPython mais idk où le trouver et peut-être qu'il existe un moyen plus simple?

Loïc Teixeira
la source

Réponses:

7

Dans except e, epeut être n'importe quelle expression Python valide:

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

[..] Pour une exceptclause avec une expression, cette expression est évaluée et la clause correspond à l'exception si l'objet résultant est «compatible» avec l'exception. Un objet est compatible avec une exception s'il s'agit de la classe ou d'une classe de base de l'objet d'exception ou d'un tuple contenant un élément compatible avec l'exception.

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

L'expression IndexError or KeyErrordonne la valeur IndexError. Donc, cela équivaut à:

except IndexError:
   ...
décomposer
la source
Merci pour cette réponse rapide d'éclairage! La considérerions-nous comme une limitation de l'analyseur (c'est-à-dire ne pouvoir accepter aucune expression plutôt qu'une expression plus spécifique) ou un choix délibéré afin de ne pas trop restreindre les choses?
Loïc Teixeira
1
Il me semble que je m'en tiens aux principes de base de Python, plus simple est meilleur. Pourquoi inventer de nouvelles règles qui ne peuvent être que contraignantes, alors qu'accepter toute expression signifie pleine liberté sans nouveaux cas particuliers?
décomposer
C'est un choix délibéré, permettant d'assembler un tuple d'exceptions à intercepter de manière dynamique et d'utiliser une telle valeur dans une exceptinstruction.
user4815162342
Je suppose que la raison de limiter les possibilités serait d'empêcher les développeurs d'écrire du code qui ne fait pas ce qu'ils avaient l'intention de faire. Après tout, écrire except IndexError or KeyErrorressemble à une chose décente à écrire. Cependant, je suis d'accord avec vous que ce serait contre d'autres valeurs que Python essaie de respecter.
Loïc Teixeira
Notez que python le permet également var == 1 or 2, ce qui à l'œil non averti "ressemble également à une chose décente à écrire".
Eric
-2

Vous devez utiliser un n-tuple de types au lieu d'une expression logique (qui renvoie simplement le premier élément non faux):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
user2622016
la source
Comme indiqué dans la question, je sais que les classes d'exception doivent être transmises en tant que tuple, mais je me demandais pourquoi l'utilisation de orPython était toujours valide.
Loïc Teixeira