Чому використання "або" в межах виключного пункту не викликає 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, але idk, де його знайти, і, можливо, є простіший спосіб?

Відповіді:


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

Дякую за швидку відповідь на освітлення! Чи вважатимемо це обмеженням парсера (тобто лише здатністю прийняти будь-який вираз, а не більш конкретним) або обдуманим вибором, щоб не обмежувати речі занадто сильно?
Loïc Teixeira

1
Мені здалося б дотримуватися основних принципів Python, щоб простіше було краще. Навіщо вигадувати нові правила, які можуть бути обмежуючими лише тоді, коли прийняття будь-якого вираження означає повну свободу без нових особливих випадків?
деге

Це обдуманий вибір, який дозволяє зібрати набір винятків, щоб динамічно фіксувати, та використовуючи таке значення у exceptвиписці.
user4815162342

Я думаю, що причиною обмеження можливостей буде те, що розробники не дозволяють писати код, який не робить те, що вони мали намір. Зрештою, писати except IndexError or KeyErrorвиглядає як гідна річ для написання. Однак я погоджуюся з вами, що це було б проти деяких інших цінностей, які Python намагається поважати.
Loïc Teixeira

Зауважте, що також дозволяє пітон var == 1 or 2, який для нетренованого ока також "виглядає як пристойна річ".
Ерік

-2

Ви повинні використовувати n-кортеж типів замість логічного виразу (який просто повертає перший неправдивий елемент):

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

Як зазначено в запитанні, я знаю, що класи виключень слід передавати як кортеж, але мені було цікаво, чому використання orвсе ще було дійсним Python.
Loïc Teixeira
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.