¿Por qué el uso de `o` dentro de una cláusula except no causa un SyntaxError? ¿Hay un uso válido para ello?

11

En el trabajo, me topé con una exceptcláusula con un oroperador:

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

Sé que las clases de excepción deben pasarse como una tupla, pero me molestó que ni siquiera causara una SyntaxError.

Así que primero quería investigar si realmente funciona. Y no lo hace.

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

Por lo tanto, no detectó la segunda excepción, y al mirar el código de bytes, se vuelve más claro por qué:

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

Para que podamos ver, la instrucción 14 primero carga la IndexErrorclase en la pila. Luego verifica si ese valor es True, lo cual es debido a la veracidad de Python y finalmente salta directamente a la instrucción 20 donde exception matchse realiza. Como la instrucción 18 se omitió, KeyErrornunca se cargó en la pila y, por lo tanto, no coincide.

Intenté con Python 2.7 y 3.6, el mismo resultado.

Pero entonces, ¿por qué es una sintaxis válida? Me imagino que es uno de los siguientes:

  1. Es un artefacto de una versión muy antigua de Python.
  2. En realidad, hay un caso de uso válido para usar ordentro de una exceptcláusula.
  3. Es simplemente una limitación del analizador de Python que podría tener que aceptar cualquier expresión después de la exceptpalabra clave.

Mi voto es sobre 3 (dado que vi un debate sobre un nuevo analizador para Python) pero espero que alguien pueda confirmar esa hipótesis. Porque si fuera 2 por ejemplo, ¡quiero saber ese caso de uso!

Además, no tengo ni idea de cómo continuaría esa exploración. Me imagino que tendría que profundizar en el código fuente del analizador CPython, pero no sé dónde encontrarlo y tal vez hay una manera más fácil.

Loïc Teixeira
fuente

Respuestas:

7

En except e, epuede haber cualquier expresión Python válida:

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

[..] Para una exceptcláusula con una expresión, esa expresión se evalúa y la cláusula coincide con la excepción si el objeto resultante es "compatible" con la excepción. Un objeto es compatible con una excepción si es la clase o una clase base del objeto de excepción o una tupla que contiene un elemento compatible con la excepción.

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

La expresión IndexError or KeyErrorproduce el valor IndexError. Entonces esto es equivalente a:

except IndexError:
   ...
difunto
fuente
Gracias por esa respuesta rápida de iluminación! ¿Lo consideraríamos una limitación del analizador (es decir, solo poder aceptar cualquier expresión en lugar de una más específica) o una elección deliberada para no restringir demasiado las cosas?
Loïc Teixeira
1
Me parecería apegarme a los principios básicos de Python de ser más simple y mejor. ¿Por qué inventar nuevas reglas que solo pueden ser limitantes, cuando aceptar cualquier expresión significa libertad total sin nuevos casos especiales?
deceze
Es una elección deliberada, que permite reunir una tupla de excepciones para atrapar dinámicamente, y usar ese valor en una exceptdeclaración.
user4815162342
Supongo que la razón para limitar las posibilidades sería evitar que los desarrolladores escriban código que no hace lo que pretendían. Después de todo, escribir except IndexError or KeyErrorparece una cosa decente para escribir. Sin embargo, estoy de acuerdo con usted en que sería en contra de otros valores que Python trata de respetar.
Loïc Teixeira
Tenga en cuenta que Python también permite var == 1 or 2, lo que para el ojo inexperto también "parece una cosa decente para escribir".
Eric
-2

Debería usar una n-tupla de tipos en lugar de una expresión lógica (que solo devuelve el primer elemento no falso):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
usuario2622016
fuente
Como se indicó en la pregunta, sé que las clases de excepción deben pasarse como una tupla, pero me preguntaba por qué el uso de orPython todavía era válido.
Loïc Teixeira