Dlaczego użycie `lub` w klauzuli wyjątku nie powoduje błędu SyntaxError? Czy ma to uzasadnione zastosowanie?

11

W pracy natknąłem się na exceptklauzulę z oroperatorem:

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

Wiem, że klasy wyjątków powinny być przekazywane jako krotka, ale wkurzyło mnie to, że nawet nie spowoduje SyntaxError.

Więc najpierw chciałem sprawdzić, czy to rzeczywiście działa. I tak nie jest.

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

Więc nie złapał drugiego wyjątku, a patrząc na kod bajtowy, staje się wyraźniejsze, dlaczego:

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

Jak widzimy, instrukcja 14 najpierw ładuje IndexErrorklasę na stos. Następnie sprawdza, czy jest to wartość True, która wynika z prawdomówności Pythona, i ostatecznie przeskakuje bezpośrednio do instrukcji 20, w której exception matchjest wykonywana. Ponieważ instrukcja 18 została pominięta, KeyErrornigdy nie została załadowana na stos i dlatego nie pasuje.

Próbowałem z Python 2.7 i 3.6, ten sam wynik.

Ale dlaczego jest to poprawna składnia? Wyobrażam sobie, że jest to jeden z następujących:

  1. To artefakt z naprawdę starej wersji Pythona.
  2. W rzeczywistości istnieje poprawny przypadek użycia orw ramach exceptklauzuli.
  3. Jest to po prostu ograniczenie parsera Pythona, który może wymagać zaakceptowania dowolnego wyrażenia po exceptsłowie kluczowym.

Mój głos jest na 3 (biorąc pod uwagę, że widziałem dyskusję na temat nowego parsera dla Pythona), ale mam nadzieję, że ktoś potwierdzi tę hipotezę. Bo jeśli na przykład było to 2, chcę poznać ten przypadek użycia!

Poza tym jestem trochę nieświadomy tego, jak kontynuowałbym tę eksplorację. Wyobrażam sobie, że musiałbym zagłębić się w kod źródłowy parsera CPython, ale nie wiem gdzie go znaleźć, a może jest łatwiejszy sposób?

Loïc Teixeira
źródło

Odpowiedzi:

7

W except e, emoże być dowolne prawidłowe wyrażenie w języku Python:

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

[..] W przypadku exceptklauzuli z wyrażeniem to wyrażenie jest oceniane, a klauzula jest zgodna z wyjątkiem, jeśli wynikowy obiekt jest „zgodny” z wyjątkiem. Obiekt jest zgodny z wyjątkiem, jeśli jest klasą lub klasą podstawową obiektu wyjątku lub krotką zawierającą element zgodny z wyjątkiem.

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

Wyrażenie IndexError or KeyErrordaje wartość IndexError. Odpowiada to:

except IndexError:
   ...
deceze
źródło
Dziękuję za to szybkie oświetlenie odpowiedzi! Czy uznalibyśmy to za ograniczenie parsera (tj. Możliwość zaakceptowania dowolnego wyrażenia, a nie bardziej konkretnego) lub celowy wybór, aby nie ograniczać zbyt wiele rzeczy?
Loïc Teixeira
1
Wydaje mi się, że trzymam się podstawowych zasad Pythona, że ​​prostsze jest lepsze. Po co wymyślać nowe reguły, które mogą ograniczać, skoro akceptacja jakiegokolwiek wyrażenia oznacza pełną swobodę bez nowych szczególnych przypadków?
deceze
Jest to celowy wybór, pozwalający na zestawienie kilku wyjątków w celu dynamicznego wychwytywania i użycie takiej wartości w exceptinstrukcji.
user4815162342
Myślę, że powodem ograniczenia możliwości byłoby uniemożliwienie programistom pisania kodu, który nie robi tego, co zamierzali. W końcu pisanie except IndexError or KeyErrorwygląda na przyzwoitą rzecz do napisania. Jednak zgadzam się z tobą, że byłoby to sprzeczne z innymi wartościami, które Python stara się uszanować.
Loïc Teixeira
Zauważ, że python również zezwala var == 1 or 2, co dla niewprawnego oka również „wygląda na przyzwoitą rzecz do napisania”.
Eric,
-2

Powinieneś użyć n-krotek typów zamiast logicznego wyrażenia (które zwraca tylko pierwszy nie-fałszywy element):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')
użytkownik2622016
źródło
Jak stwierdzono w pytaniu, wiem, że klasy wyjątków powinny być przekazywane jako krotka, ale zastanawiałem się, dlaczego używanie orwciąż było poprawnym Pythonem.
Loïc Teixeira