Почему использование `или` в условии кроме не вызывает SyntaxError? Есть ли действительное использование для этого?

11

На работе я наткнулся на exceptоговорку с orоператором:

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

Я знаю, что классы исключений должны передаваться как кортеж, но меня это беспокоило, что это даже не вызвало бы SyntaxError.

Итак, сначала я хотел выяснить, действительно ли это работает. И это не так.

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

Так что он не уловил второе исключение, и, глядя на байт-код, становится понятнее, почему:

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

Итак, мы видим, что инструкция 14 сначала загружает IndexErrorкласс в стек. Затем он проверяет, является ли это значение Trueверным для Python, и, наконец, сразу переходит к инструкции 20, где exception matchвыполняется. Поскольку инструкция 18 была пропущена, KeyErrorона никогда не загружалась в стек и поэтому не совпадает.

Я пытался с Python 2.7 и 3.6, тот же результат.

Но тогда, почему это правильный синтаксис? Я представляю, что это одно из следующего:

  1. Это артефакт из действительно старой версии Python.
  2. На самом деле есть действительный вариант использования для использования orв exceptпредложении.
  3. Это просто ограничение синтаксического анализатора Python, который может принимать любое выражение после exceptключевого слова.

Мой голос за 3 (учитывая, что я видел некоторую дискуссию о новом парсере для Python), но я надеюсь, что кто-то может подтвердить эту гипотезу. Потому что, если это было, например, 2, я хочу знать этот вариант использования!

Кроме того, я немного не понимаю, как мне продолжить это исследование. Я полагаю, мне нужно было бы покопаться в исходном коде парсера CPython, но не знаю, где его найти, и, возможно, есть более простой способ?

Лоик Тейшейра
источник

Ответы:

7

В except e, eможет быть любое допустимое выражение Python:

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

[..] Для exceptпредложения с выражением это выражение оценивается, и предложение соответствует исключению, если результирующий объект «совместим» с исключением. Объект совместим с исключением, если это класс или базовый класс объекта исключения или кортеж, содержащий элемент, совместимый с исключением.

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

Выражение IndexError or KeyErrorдает значение IndexError. Так что это эквивалентно:

except IndexError:
   ...
децезе
источник
Спасибо за быстрое решение освещения! Считаем ли мы это ограничением синтаксического анализатора (т. Е. Только возможностью принимать какое-либо выражение, а не более конкретное) или осознанным выбором, чтобы не слишком ограничивать вещи?
Лойк Тейшейра
1
Мне кажется, что я придерживаюсь базовых принципов Python: проще быть лучше. Зачем придумывать новые правила, которые могут быть только ограничивающими, когда принятие любого выражения означает полную свободу без новых особых случаев?
deceze
Это преднамеренный выбор, позволяющий собирать набор исключений для динамического перехвата и использующий использование такого значения в exceptвыражении.
user4815162342
Я предполагаю, что причиной ограничения возможностей было бы то, что разработчики не могли писать код, который не выполняет то, что они намеревались. В конце концов, письмо except IndexError or KeyErrorвыглядит как достойная вещь для написания. Однако я согласен с вами, что это будет против некоторых других ценностей, которые Python пытается уважать.
Лойк Тейшейра
Обратите внимание, что Python также позволяет var == 1 or 2, что для неопытного глаза также "выглядит как достойная вещь для написания".
Эрик
-2

Вы должны использовать n-кортеж типов вместо логического выражения (которое просто возвращает первый не ложный элемент):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
user2622016
источник
Как указано в вопросе, я знаю, что классы исключений должны передаваться как кортеж, но мне было интересно, почему использование orвсе еще действующего Python.
Лойк Тейшейра