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

На работе я наткнулся на предложение 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, но не знаю, где его найти, и, может быть, есть более простой способ?

Ответы [ 2 ]

7 голосов
/ 08 октября 2019

В 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:
   ...
0 голосов
/ 08 октября 2019

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

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
...