Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does using `or` within an except clause not cause a SyntaxError? Is there a valid use for it?

At work, I stumbled upon an except clause with an or operator:

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

I know the exception classes should be passed as a tuple, but it bugged me that it wouldn't even cause a SyntaxError.

So first I wanted to investigate whether it actually works. And it doesn't.

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

So it did not catch the second exception, and looking at the bytecode, it becomes clearer why:

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

So we can see, instruction 14 first loads the IndexError class onto the stack. Then it checks whether that value is True, which it is because of Python truthiness and finally jumps directly to instruction 20 where the exception match is done. Since instruction 18 was skipped, KeyError was never loaded onto the stack and therefore doesn't match.

I tried with Python 2.7 and 3.6, same result.

But then, why is it valid syntax? I imagine it being one of the following:

  1. It's an artifact from a really old version of Python.
  2. There is actually a valid use case for using or within an except clause.
  3. It's simply a limitation of the Python parser which might have to accept any expression after the except keyword.

My vote is on 3 (given I saw some discussion about a new parser for Python) but I'm hoping someone can confirm that hypothesis. Because if it was 2 for example, I want to know that use case!

Also, I'm a bit clueless on how I'd continue that exploration. I imagine I would have to dig into CPython parser's source code but idk where to find it and maybe there's an easier way?

like image 382
Loïc Teixeira Avatar asked Oct 08 '19 11:10

Loïc Teixeira


People also ask

Why is except invalid syntax Python?

The syntax error exception occurs when the code does not conform to Python keywords, naming style, or programming structure. The interpreter sees the invalid syntax during its parsing phase and raises a SyntaxError exception.

Can try except be used to catch invalid keyboard input?

You can catch that exception using the try-except block. The try block lets you test a block of code for errors, the except block handles the errors and the finally block lets you execute code regardless of errors.

What happens if except clause is written without any exception type explain with an example?

If the statements in this block are executed without an exception, the subsequent except: block is skipped. If the exception does occur, the program flow is transferred to the except: block. The statements in the except: block are meant to handle the cause of the exception appropriately.

What happens if an error is not caught in Python?

When an exception occurs, the Python interpreter stops the current process. It is handled by passing through the calling process. If not, the program will crash. For instance, a Python program has a function X that calls function Y, which in turn calls function Z.


1 Answers

In except e, e can be any valid Python expression:

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

[..] For an except clause with an expression, that expression is evaluated, and the clause matches the exception if the resulting object is “compatible” with the exception. An object is compatible with an exception if it is the class or a base class of the exception object or a tuple containing an item compatible with the exception.

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

The expression IndexError or KeyError yields the value IndexError. So this is equivalent to:

except IndexError:
   ...
like image 182
deceze Avatar answered Oct 07 '22 00:10

deceze