Por que o uso de `ou` dentro de uma cláusula exceto não causa um SyntaxError? Existe um uso válido para isso?

11

No trabalho, deparei-me com uma exceptcláusula com um oroperador:

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

Eu sei que as classes de exceção devem ser passadas como uma tupla, mas me incomodou que nem causasse a SyntaxError.

Então, primeiro eu queria investigar se realmente funciona. E isso não acontece.

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

Portanto, ele não capturou a segunda exceção e, olhando o bytecode, fica mais claro o motivo:

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

Como podemos ver, a instrução 14 primeiro carrega a IndexErrorclasse na pilha. Em seguida, verifica se esse valor é o Trueque é devido à veracidade do Python e, por fim, pula diretamente para a instrução 20 onde exception matché feito. Como a instrução 18 foi pulada, KeyErrornunca foi carregada na pilha e, portanto, não corresponde.

Eu tentei com Python 2.7 e 3.6, mesmo resultado.

Mas então, por que é uma sintaxe válida? Eu imagino que seja um dos seguintes:

  1. É um artefato de uma versão muito antiga do Python.
  2. Na verdade, existe um caso de uso válido para uso orem uma exceptcláusula.
  3. É simplesmente uma limitação do analisador Python que pode ter que aceitar qualquer expressão após a exceptpalavra - chave.

Meu voto é em 3 (dado que vi algumas discussões sobre um novo analisador para Python), mas espero que alguém possa confirmar essa hipótese. Porque se fosse 2, por exemplo, eu quero saber esse caso de uso!

Além disso, sou um pouco ignorante sobre como eu continuaria essa exploração. Eu imagino que eu teria que cavar no código fonte do analisador CPython, mas idk onde encontrá-lo e talvez haja uma maneira mais fácil?

Loïc Teixeira
fonte

Respostas:

7

In except e, epode ser qualquer expressão válida do Python:

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

[..] Para uma exceptcláusula com uma expressão, essa expressão é avaliada e a cláusula corresponde à exceção se o objeto resultante for "compatível" com a exceção. Um objeto é compatível com uma exceção se for a classe ou uma classe base do objeto de exceção ou uma tupla contendo um item compatível com a exceção.

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

A expressão IndexError or KeyErrorproduz o valor IndexError. Portanto, isso é equivalente a:

except IndexError:
   ...
deceze
fonte
Obrigado por essa resposta rápida de iluminação! Consideraríamos uma limitação do analisador (isto é, apenas ser capaz de aceitar qualquer expressão em vez de uma expressão mais específica) ou uma escolha deliberada para não restringir demais as coisas?
Loïc Teixeira
11
Parece-me que me ater aos princípios básicos do Python de ser mais simples. Por que inventar novas regras que só podem ser limitativas, quando aceitar qualquer expressão significa liberdade total sem novos casos especiais?
deceze
É uma escolha deliberada, permitindo reunir uma tupla de exceções para capturar dinamicamente e usar esse valor em uma exceptdeclaração.
precisa saber é o seguinte
Eu acho que a razão para limitar as possibilidades seria impedir que os desenvolvedores escrevessem código que não faz o que eles pretendiam. Afinal, escrever except IndexError or KeyErrorparece uma coisa decente para escrever. No entanto, concordo com você que seria contra outros valores que o Python tenta respeitar.
Loïc Teixeira
Note que o python também permite var == 1 or 2, que para os olhos destreinados também "parece uma coisa decente para escrever".
Eric
-2

Você deve usar uma n-tupla de tipos em vez de uma expressão lógica (que retorna apenas o primeiro elemento não falso):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
user2622016
fonte
Conforme declarado na pergunta, eu sei que as classes de exceção devem ser passadas como uma tupla, mas fiquei pensando por que o uso de orPython ainda era válido.
Loïc Teixeira